一、预处理
1、什么是预处理
C语言的编译过程过程如下:
.c文件=>
预处理→.i文件
=>编译→.s文件
=>汇编→.o文件
=>链接→可执行文件
我们创建一个helloworld.c的文件,通过一个命令可以只执行预处理步骤,命令如下:
gcc -o helloworld.i helloworld.c -E
-E就是只做预处理的意思。
预处理的本质就是将引入的头文件展开,即将头文件的内容直接写入到引入它的文件中去,此外还了宏替换。
2、什么是宏
(1)宏常量
C语言常量分为直接常量和符号常量,宏就是符号常量。宏的本质是在预处理阶段发生的单纯的字符串替换(宏替换),在预处理阶段,宏的替换完全不考虑C语言的语法。宏的定义方法如下:
#define 标识符 常量值
注意,在定义语句的结尾没有分号。
我们定义一个test.c文件:
#include <stdio.h>
#define R 10
#define M int main(
M){
int a = R;
printf("The value of a is %d\n", a);
printf("Hello world!\n");
return 0;
}
看起来很混乱,没有遵循语法,但是我们编译并执行以下试试:
image.png
可以看到程序正常执行。
通常在实际项目中,如果有什么东西是需要批量替换的,都会事先用宏定义好。
(2)宏函数
宏替换只是简单得字符串替换,且可以传递参数。例如:
#define N(n) n*10
int b = N(a); // 首先n替换成a,然后a*10
如果:
#define ADD(a,b) a+b
int c = ADD(a,b) * ADD(a,b);
经过宏替换会先得到a+b*a+b,再去做计算,这显然不是我们想要的结果,因此加上括号保证优先级不会出错。
#define ADD(a,b) (a+b)
宏函数的好处是,宏定义的函数中,参数不需要写死变量类型,因为宏替换只是先用字符串替换,不管是什么类型,都会先以字符串的形式带入到函数中,我们只要关心输出的结果是什么类型就可以了。
比如,使用ADD函数的时候,可以使a、b为整型传入也可以用浮点型传入,而程序定义的形参则会限制参数的类型。
(3)条件编译
条件编译是指预处理器根据条件编译指令,有条件地选择源程序代码中的一部分代码作为输出,送给编译器进行编译。主要是为了有选择性地执行相应操作,防止宏替换内容(如文件等)的重复包含。常见的条件编译指令如表 1 所示。
表 1 常见的条件编译指令
条件编译指令 | 说 明 |
---|---|
#if | 如果条件为真,则执行相应操作 |
#elif | 如果前面条件为假,而该条件为真,则执行相应操作 |
#else | 如果前面条件均为假,则执行相应操作 |
#endif | 结束相应的条件编译指令 |
#ifdef | 如果该宏已定义,则执行相应操作 |
#ifndef | 如果该宏没有定义,则执行相应操作 |
#if-#else-#endif
其调用格式为:
#if 条件表达式
程序段1
#else
程序段2
#endif
功能为:如果#if后的条件表达式为真,则程序段 1 被选中,否则程序段 2 被选中。
注意,必须使用 #endif 结束该条件编译指令。
例如:
#include<stdio.h>
#define RESULT 0//定义 RESULT 为 0
int main (void)
{
#if !RESULT //或者 0==RESULT
printf("It's False!\n");
#else
printf("It's True!\n");
#endif //标志结束#if
return 0;
}
上述程序中,首先定义了 RESULT 为 0,在 main 中使用 #if-#else-#endif 条件判断语句,如果 RESULT 为 0,则输出 It's False!,否则输出 It's True!。本例输出为:It's False!。
#ifndef-#define-#endif
其调用格式为:
#ifndef 标识符
#define 标识符 替换列表
//...
#endif
功能为:一般用于检测程序中是否已经定义了名字为某标识符的宏,如果没有定义该宏,则定义该宏,并选中从 #define 开始到 #endif 之间的程序段;如果已定义,则不再重复定义该符号,且相应程序段不被选中。
例如:
#ifndef PI
#define PI 3.1416
#endif
上述程序段,用于判断是否已经定义了名为 PI 的宏,如果没有定义 PI,则执行如下宏定义。
#define PI 3.1416
如果检测到已经定义了 PI,则不再重复执行上述宏定义。
该条件编译指令更重要的一个应用是防止头文件重复包含。
如果 f.c 源文件中包含 f1.h 和 f2.h 两个头文件,而 f1.h 头文件及 f2.h 头文件中均包含 x.h 头文件,则 f.c 源文件中重复包含 x.h 头文件。可采用条件编译指令,来避免头文件的重复包含问题。所有头文件中都按如下格式:
#ifndef _HEADNAME_H_
#define _HEADNAME_H_
//头文件内容
#endif
如果该头文件第一次被包含时,由于没检测到该头文件名对应的符号(宏名)_HEADNAME_H_
,则定义该头文件名对应的符号(宏),其值为该系统默认。并且,该条件编译指令选中 #endif 之前的头文件内容;
如果该头文件再次被包含时,由于检测到已存在以该头文件名对应的符号(宏名),则忽略该条件编译指令之间的所有代码,从而避免了重复包含。
#if-#elif-#else-#endif
其调用格式为:
#if 条件表达式1
程序段 1
#elif 条件表达式2
程序段 2
#else
程序段3
#endif
功能为:先判断条件1的值,如果为真,则程序段 1 被选中编译;如果为假,而条件表达式 2 的值为真,则程序段 2 被选中编译;其他情况,程序段 3 被选中编译。
#ifdef-#endif
其调用格式为:
#ifdef 标识符
程序段
#endif
功能为:如果检测到已定义该标识符,则选择执行相应程序段被选中编译;否则,该程序段会被忽略。
例如:
#ifdef N
#undef N
//程序段
#endif
功能:如果检测到符号 N 已定义,则删除其定义,并选中相应的程序段。
(4)typedef函数
typedef的作用是个一个变量数据类型起别名。
语法:typedef [数据类型] [别名];
eg.typedef int * pointer;//将int * 的数据类型命名为pointer
typedef 和宏中的#define的区别:
①预处理时#define定义的宏会替换,但typedef定义的别名不会替换。
②作用域不同,宏的作用域是全局,而typede的作用域如果在全局中定义就是全局,如果在一个函数体内定义则就只在当前函数体。
③通常用于给自定义数据类型其别名,例如系统有个size_t类型,其真实面目就是unsigned long的别名,系统做了如下定义:
typedef unsigned long size_t;
二、结构体
C 数组允许定义可存储相同类型数据项的变量,结构体是 C 编程中另一种用户自定义的可用的数据类型,它允许我们存储不同类型的数据项。
结构体用于表示一条记录,假设我们想要跟踪图书馆中书本的动态,我们可能需要跟踪每本书的下列属性:
·Title
·Author
·Subject
·Book ID
1、结构体的声明与定义
为了定义结构体,我们必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格式如下:
struct [结构名称]
{
member definition;
member definition;
...
member definition;
} [one or more structure variables];
结构名称是可写可不写的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在结构定义的末尾,最后一个分号之前,我们可以指定一个或多个结构变量,这也是可写可不写的,例如:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
2、结构体的初始化和引用
为了访问结构的成员,我们使用成员访问运算符(.)。成员访问运算符是结构变量名称和我们要访问的结构成员之间的一个句点。可以使用 struct 关键字来定义结构类型的变量。下面的实例演示了结构的用法:
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Book */
struct Books Book2; /* 声明 Book2,类型为 Book */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
image.png
我们也可以像数组一样,在声明一个结构对象的时候就把属性值以参数的形式传递进去做初始化,同样,对于已经声明过的结构体,我们还可以声明一个这个结构体类型的数组,像下面这样:
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Book */
struct Books Book2; /* 声明 Book2,类型为 Book */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 补充Book3和Book4的详述,并吧四本书的对象放到Books类型的数组中 */
struct Books Books[4] = {Book1, Book2, {"PHP", "Php Ali", "PHP Programming",6543210},{"Python", "Python Ali", "Python Programming", 65478930}};
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Books[0].title);
printf( "Book 1 author : %s\n", Books[0].author);
printf( "Book 1 subject : %s\n", Books[0].subject);
printf( "Book 1 book_id : %d\n", Books[0].book_id);
/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Books[1].title);
printf( "Book 2 author : %s\n", Books[1].author);
printf( "Book 2 subject : %s\n", Books[1].subject);
printf( "Book 2 book_id : %d\n", Books[1].book_id);
/* 输出 Book3 信息 */
printf( "Book 3 title : %s\n", Books[2].title);
printf( "Book 3 author : %s\n", Books[2].author);
printf( "Book 3 subject : %s\n", Books[2].subject);
printf( "Book 3 book_id : %d\n", Books[2].book_id);
/* 输出 Book4 信息 */
printf( "Book 4 title : %s\n", Books[3].title);
printf( "Book 4 author : %s\n", Books[3].author);
printf( "Book 4 subject : %s\n", Books[3].subject);
printf( "Book 4 book_id : %d\n", Books[3].book_id);
return 0;
}
Book3和Book4是在声明的时候就传入了初始化值,同时把Book1234都塞入到了数组中,我们可以看到四本书的信息均可以正常输出:
image.png
3、结构体指针
我们可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
然后在上述定义的指针变量中存储结构变量的地址。为了查找结构变量的地址,把 & 运算符放在结构名称的前面,如下所示:
struct_pointer = &Book1;
了解了结构体指针之后,我们就拥有了3种调用成员变量的方法除了使用结构体变量名称.成员变量名
的方法之外,针对于我们创造的struct_pointer,我们还可以使用(*struct_pointer).成员变量名
和p->成员变量名
的方法。
下面我们对上一节的程序脚本做个改造,看一看怎样使用结构体指针。
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Book */
struct Books Book2; /* 声明 Book2,类型为 Book */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 补充Book3和Book4的详述,并吧四本书的对象放到Books类型的数组中 */
struct Books Books[4] = {Book1, Book2, {"PHP", "Php Ali", "PHP Programming",6543210},{"Python", "Python Ali", "Python Programming", 65478930}};
struct Books * pointer1;
pointer1= &Book1; //将结构体指针pointer1指向Book1
struct Books * pointer2;
pointer2= Books; //将结构体指针pointer1指向数组Books
pointer2++;
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Books[0].title);
printf( "Book 1 title : %s\n", (*pointer1).title); //注意,(*pointer1)的括号不能省略,否则系统会认为是pointer1.title的✳
printf( "Book 1 title : %s\n", pointer1->title); //注意,(*pointer1)的括号不能省略,否则系统会认为是pointer1.title的✳
printf( "Book 1 author : %s\n", Books[0].author);
printf( "Book 1 subject : %s\n", Books[0].subject);
printf( "Book 1 book_id : %d\n", Books[0].book_id);
/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Books[1].title);
printf( "Book 2 title : %s\n", pointer2->title); //只能->,不能用点,应为pinter2是数组的指针
printf( "Book 2 author : %s\n", Books[1].author);
printf( "Book 2 subject : %s\n", Books[1].subject);
printf( "Book 2 book_id : %d\n", Books[1].book_id);
/* 输出 Book3 信息 */
printf( "Book 3 title : %s\n", Books[2].title);
printf( "Book 3 author : %s\n", Books[2].author);
printf( "Book 3 subject : %s\n", Books[2].subject);
printf( "Book 3 book_id : %d\n", Books[2].book_id);
/* 输出 Book4 信息 */
printf( "Book 4 title : %s\n", Books[3].title);
printf( "Book 4 author : %s\n", Books[3].author);
printf( "Book 4 subject : %s\n", Books[3].subject);
printf( "Book 4 book_id : %d\n", Books[3].book_id);
return 0;
}
编译并执行这个脚本,我们看到正常输出了结构体的属性值:
image.png
pointer2= Books
而不是pointer2= &Books
这部分是有难点的。
数组的名字代表了这个数组的内存首地址,数组括号内的长度代表了数组的单元数,数据类型是int的话就按照int类型(32位系统上是4个字节)乘以单元数的长度,如果数据类型是结构体的话就按照结构体的长度乘以单元的长度。
总之数组名字代表了这个数组的内存首地址。
pointer2++,不是内存位置右移了一个角标,而是右移了一个单元长度的结构体Books的内存长度。所以就不难理解为什么右移到了第二个结构体实例的首地址上了。
四、共用体
共用体是一种特殊的数据类型,也叫联合体允许,他允许我们在相同的内存位置存储不同的数据类型。我们可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。
共用体提供了一种使用相同的内存位置的有效方式,可以大大节省内存空间
但是也有一个缺点,那就是它的有效值只是最后一次赋的值。
1、定义共用体
为了定义共用体,我们必须使用 union 语句,方式与定义结构类似。union 语句定义了一个新的数据类型,带有多个成员。union 语句的格式如下:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
union tag 是可选的,每个 member definition 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在共用体定义的末尾,最后一个分号之前,您可以指定一个或多个共用体变量,这是可选的。下面定义一个名为 Data 的共用体类型,有三个成员 i、f 和 str:
union Data
{
int i;
float f;
char str[20];
} data;
现在,Data 类型的变量可以存储一个整数、一个浮点数,或者一个字符串。这意味着一个变量(相同的内存位置)可以存储多个多种类型的数据。我们可以根据需要在一个共用体内使用任何内置的或者用户自定义的数据类型。
共用体占用的内存应足够存储共用体中最大的成员。例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
2、访问共用体成员
为了访问共用体的成员,我们使用成员访问运算符(.)。成员访问运算符是共用体变量名称和我们要访问的共用体成员之间的一个句号。使用 union 关键字来定义共用体类型的变量。下面的实例演示了共用体的用法:
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
data.i = 10;
data.f = 220.5;
strcpy( data.str, "C Programming");
printf( "data.i : %d\n", data.i);
printf( "data.f : %f\n", data.f);
printf( "data.str : %s\n", data.str);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
image.png
在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。现在让我们再来看一个相同的实例,这次我们在同一时间只使用一个变量,这也演示了使用共用体的主要目的:
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
data.i = 10;
printf( "data.i : %d\n", data.i);
data.f = 220.5;
printf( "data.f : %f\n", data.f);
strcpy( data.str, "C Programming");
printf( "data.str : %s\n", data.str);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
image.png
在这里,所有的成员都能完好输出,因为同一时间只用到一个成员。
五、链表
整型、浮点型、数组这些数据结构,都是静态的,他们所占的存储空间都是固定大小。链表则不是这样,它的每一个结点都是在程序中定义的而不是临时开辟的。
1、静态链表
静态链表每一个节点包含两个部分,一部分是用户需要的数据,一部分是下一个节点的地址,最开始是由一个head头指向下体个节点,其结构如下图所示:
image.png
链表里各个元素的内存地址是不连续的。
我们用代码来构造一个链表:
#include <stdio.h>
/*构造一个武器类*/
struct weapon{
int price; //价格
int atk; //攻击力
struct weapon *next;//下一个节点的信息
};
int main(){
struct weapon a,b,c,*head;
a.price=100;
a.atk=100;
b.price=200;
b.atk=200;
c.price=300;
c.atk=300;
head=&a;
a.next=&b;
b.next=&c;
c.next= NULL;
struct weapon *p;
p=head;
while(p!= NULL){
printf("price:%d, attack:%d \n",p->price,p->atk);
p=p->next;
}
}
执行结果如下:
image.png
这种链表不是临时开辟的,也不能用完后释放,所以被成为静态链表。
2、建立动态链表
所谓建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相链的联系。
(1)相关的函数
链表的结构是动态地分配内存,即在需要时才需要一个存储单元,以便插入或追加节点,删除结点后需要释放节点所使用的内存单元,C语言提供了相应函数:
1)malloc函数
void * malloc(unsigned int size)
,在内存的动态存储区中分配一个长度为size的连续空间,成功则返回void类型的空指针,否则返回NULL。
2)free函数
void free(ptr)
,释放ptr所指向的内存空间。
3)calloc函数
void * calloc(unsignd int n,unsigned int size)
,在内存的动态存储区中分配n个长度为size的连续空间,成功则返回分配域的起始地址,否则返回0。
(2)创建动态链表
我们用代码演示一下动态链表的生成:
#include <stdio.h>
#include <malloc.h> //使用malloc函数必须先引入malloc.h库
/*构造一个武器类*/
struct weapon{
int price; //价格
int atk; //攻击力
struct weapon *next;//下一个节点的信息
};
struct weapon * create(){
struct weapon *head, *last, *p;
int num = 0;
p = (struct weapon *)malloc(sizeof(struct weapon));
scanf("%d, %d", &p->price, &p->atk);
while(p->price!=0){
if(num == 0){
head = p;
}else{
last->next = p;
}
last = p; //在last中记录已有的p的对象
p = (struct weapon *)malloc(sizeof(struct weapon)); //p中装在一个新的对象
scanf("%d, %d", &p->price, &p->atk);
num++;
}
last->next = NULL;
return head;
}
int main(){
struct weapon *a;
a = create();
printf("price:%d",a->price);
}
运行结果就是显示了第一个结点的价格:
image.png
6、位运算
(1)运算定义
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:
A = 0011 1100
B = 0000 1101
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
注意:位运算是用变量的补码来计算的,只有int类型和char类型可以用位运算。一个数或者字符的最高位为符号位,0表示正数,1表示负数。正数的补码等于本身,负数的补码等于反码+1,正数的反码等于本身,负数的反码除符号位外,各位取反。
下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与操作,按二进制位进行“与”运算。运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1;
|
(A & B) 将得到 12,即为 0000 1100 |
| | 按位或运算符,按二进制位进行“或”运算。运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1;
|
(A | B) 将得到 61,即为0011 1101 |
^ | 异或运算符,按二进制位进行“异或”运算。运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0
|
(A ^ B) 将得到 49,即为 0011 0001 |
~ | 取反运算符,按二进制位进行“取反”运算。运算规则:~1=0; ~0=1;
|
(~A ) 将得到 -61,即为 1100 0011,2 的补码形式,带符号的二进制数。 |
<< | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数(正数左补0,负数左补1,右边丢弃)。 | A >> 2 将得到 15,即为 0000 1111 |
代码实例:
#include <stdio.h>
int main()
{
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c );
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c );
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c );
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c );
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c );
}
运行后的结果是:
image.png
(2)应用
1)按位与
1.对变量迅速清零,只要让这个变量和0做按位与运算即可;
2.保留数据的指定位,比如我们想保留数a(32位)的第一把位(前8位),只要让它和数字b(第一把位全是1)按位与运算,即可获取;
3.判断一个数是奇数还是偶数,让一个数和1做按位与运算,如果结果是1就是奇数,如果是0就是偶数。
2)按位或
将数据的指定位都置1,比如我们想把数a(32位)的第一把位(前8位)全置为1,只要让它和数字b(第一把位全是1)按位或运算,即可做到;
3)按位亦异或
1.将数据的指定位全都反转,即0变成1,1变成0,比如我们想把数a(32位)的第一把位(前8位)全部反转,只要让它和数字b(第一把位全是1)按位异或运算,即可做到;
2.数值交换:假设a=9,b=5,我们只要做如下操作:
a = a^b;
b = b^a;
a = a^b;
即可交换数值,此时a=5,b=9。这种交换数值方法,比使用第三种变量做容器要快得多。
4)左移右移
一个数左移n位,就相当于将其乘以2的n次方。同理,一个数右移动n位,就相当于将其除以2的n次方。负数在右移之后,符号位是1还是0,根据不同的操作系统会有不同的结果。
7、函数递归
递归是以自相似的方式重复项目的处理过程。说白了,就是在编程语言中,在函数内部调用函数自身,称为递归调用。如下:
void recursion(){
recursion(); /* 函数调用自身 */
}
int main(){
recursion();
}
C 语言支持递归,即,一个函数可以调用自身。但在使用递归时,程序员需要注意定义一个从函数退出的条件,否则会进入无限循环。
递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。
(1)数的阶乘
下面的实例使用递归函数计算一个给定的数的阶乘:
#include <stdio.h>
int factorial(unsigned int i){
if(i <= 1){
return 1;
}
return i * factorial(i - 1);
}
int main() {
int i = 15;
printf("Factorial of %d is %d\n", i, factorial(i));
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
image.png
(2)斐波那契数列
下面的实例使用递归函数生成一个给定的数的斐波那契数列:
#include <stdio.h>
int fibonaci(int i){
if(i == 0){
return 0;
}
if(i == 1){
return 1;
}
return fibonaci(i-1) + fibonaci(i-2);
}
int main(){
int i;
for (i = 0; i < 10; i++){
printf("%d\t%n", fibonaci(i));
}
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
image.png
(2)递归与递推的区别
递归:将一个问题规模为n的问题降为规模为(n-1)的问题,然后依次降低规模(层层往下),直至问题得到低规模的解,之后依次带入高规模的问题中(层层往上),最终得到规模为n解。从n → 1→ n;
递推:先构造解决一个低的规模问题,然后依次(层层往上)推导出高规模的问题解。从1 → n;
网友评论