C语言复习总结:
这个文件是在学习完C语言的基本用法后,回顾C语言的一些语法和易错,不易理解的一下内容,可能会存在内容不连续的情况.
本文内容,有参考自M了个J的博客根据自身的学习进行了部分的摘抄和扩写
原文请参考:http://www.cnblogs.com/mjios/tag/objective-c/default.html?page=1
图片 没 传成功 我稍后再试试
预处理指令:
一、宏定义:
1、不带参数的宏定义
#define宏名字符串
#define ABC 10
作用:在编译预处理时,将源程序中所有"宏名"替换成右边的"字符串",常用来定义常量。
使用习惯与注意
1宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误
2对程序中用双引号扩起来的字符串内的字符(字符串),不进行宏的替换操作。
3定义一个宏时可以引用已经定义的宏名
4宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令
5在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查
2、带参数的宏定义
#define 宏名(参数列表)字符串
#define average(a, b) (a+b)/2
将源程序中所有宏名替换成字符串,并且将字符串中的参数用宏名右边参数列表中的参数替换
使用注意
宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串
#define average (a, b) (a+b)/2
2
3int main ()
4 {
5 int a = average(10,4);// int a = (a, b) (a+b)/2(10,4);
6 return0;
7 }
2> 带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。
下面定义一个宏D(a),作用是返回a的2倍数值:
- 如果定义宏的时候不用小括号括住参数
1 #include <stdio.h>
2
3#defineD(a) 2*a
4
5int main ()
6 {
7 int b =D(3+4);
8
9 printf("%d", b);
10 return0;
11 }
第7行将被替换成int b = 2*3+4;,输出结果:
- 如果定义宏的时候用小括号括住参数,把上面的第3行改成:
#defineD(a) 2*(a)
注意右边的a是有括号的,第7行将被替换成int b = 2*(3+4);,输出结果:
3> 计算结果最好也用括号括起来
下面定义一个宏P(a),作用是返回a的平方:
- 如果不用小括号括住计算结果
1 #include <stdio.h>
2
3#definePow(a) (a) * (a)
4
5int main(int argc,constchar * argv[]) {
6 int b =Pow(10) /Pow(2);
7
8 printf("%d", b);
9 return0;
10 }
注意第3行,没有用小括号扩住计算结果,只是括住了参数而已。第6行代码被替换为:
int b = (10) * (10) / (2) * (2);
简化之后:int b = 10 * (10 / 2) * 2;,最后变量b为:
- 如果用小括号括住计算结果
将上面的第3行代码改为:
#definePow(a) ( (a) * (a) )
那么第6行被替换为:
int b = ( (10) * (10) ) / ( (2) * (2) );
简化之后:int b = (10 * 10) / (2 * 2);,最后输出结果:。这个才是我们想要的结果。
也就意味着前面的#define average(a, b) (a+b)/2应该写成#define average(a, b) (((a)+(b))/2)
二、条件编译
基本用法
1#if条件1
2 ...code1...
3#elif条件2
4 ...code2...
5#else
6 ...code3...
7#endif
1> 如果条件1成立,那么编译器就会把#if与#elif之间的code1代码编译进去(注意:是编译进去,不是执行,很平时用的if-else是不一样的)
2>如果条件1不成立、条件2成立,那么编译器就会把#elif与 #else之间的code2代码编译进去
3> 如果条件1、2都不成立,那么编译器就会把#else与 #endif之间的code3编译进去
4> 注意,条件编译结束后,要在最后面加一个#endif,不然后果很严重(自己思考一下后果)
5> #if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义
其他用法
1.#if defined()和#if!defined()的用法
#if和 #elif后面的条件不仅仅可以用来判断宏的值,还可以判断是否定义过某个宏。比如:
1#ifdefined(MAX)
2 ...code...
3#endif
如果前面已经定义过MAX这个宏,就将code编译进去。它不会管MAX的值是多少,只要定义过MAX,条件就成立。
条件也可以取反:
1#if !defined(MAX)
2 ...code...
3#endif
如果前面没有定义过MAX这个宏,就将code编译进去。
2.#ifdef和#ifndef的使用
* #ifdef的使用和#ifdefined()的用法基本一致
1#ifdefMAX
2 ...code...
3#endif
如果前面已经定义过MAX这个宏,就将code编译进去。
* #ifndef又和#if !defined()的用法基本一致
1#ifndefMAX
2 ...code...
3#endif
如果前面没有定义过MAX这个宏,就将code编译进去。
上面的#ifdef和#ifndef可以很好地解决 重复声明头文件的现象
三、文件包含
.第1种形式#include <文件名>
直接到C语言库函数头文件所在的目录中寻找文件
第2种形式 #include "文件名"
系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找
使用注意:
1.#include指令允许嵌套包含,比如a.h包含b.h,b.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.h,b.h包含 a.h
2、使用#include指令可能导致多次包含同一个头文件,降低编译效率
解决方法,使用条件编译,判断是否已经定义了某个某文件,如果是不在进行重复定义,否则,在进行定义
变量的存储类型:
1.自动变量
1>定义:自动变量是存储在堆栈中的。
2>哪些是自动变量:所有的局部变量在默认情况下都是自动变量。
3> 生命周期:在程序执行到声明自动变量的代码块(函数)时,自动变量才被创建;当自动变量所在的代码块(函数)执行完毕后,这些自动变量就会自行销毁。如果一个函数被重复调用,这些自动变量每次都会重新创建。
2.静态变量
1> 定义:静态变量是存储在静态内存中的,也就是不属于堆栈。
2> 哪些是静态变量:
- 所有的全局变量都是静态变量
- 被关键字static修饰的局部变量也是静态变量
3> 生命周期:静态变量在程序运行之前创建,在程序的整个运行期间始终存在,直到程序结束。
1 #include <stdio.h>
2
3int a;//全局变量为静态变量只会执行一次
4
5void test() {
6 staticint b =0; //全局变量为静态变量只会执行一次
7 b++;
8
9 int c =0;
10 c++;
11
12 printf("b=%d, c=%d \n", b, c);
13 }
14
15int main() {
16 int i;
17 // 连续调用3次test函数
18 for (i =0; i<3; i++) {
19 test();
20 }
21
22 return0;
23 }
* 第3行的变量a、第6行的变量b都是静态变量,第9行的变量c、第16行的变量i是自动变量。
* 因为第6行的变量b是静态变量,所以它只会被创建一次,而且生命周期会延续到程序结束。因为它只会创建一次,所以第6行代码只会执行一次,下次再调用test函数时,变量b的值不会被重新初始化为0。
* 注意:虽然第6行的变量b是静态变量,但是只改变了它的存储类型(即生命周期),并没有改变它的作用域,变量b还是只能在test函数内部使用。
* 我们在main函数中重复调用test函数3次,输出结果为:
static、extern与函数
1.static
* 在定义函数时,在函数的最左边加上static可以把该函数声明为内部函数(又叫静态函数),这样该函数就只能在其定义所在的文件中使用。如果在不同的文件中有同名的内部函数,则互不干扰。
* static也可以用来声明一个内部函数
2.extern
* 在定义函数时,如果在函数的最左边加上关键字extern,则表示此函数是外部函数,可供其他文件调用。C语言规定,如果在定义函数时省略extern,则隐含为外部函数。
* 在一个文件中要调用其他文件中的外部函数,则需要在当前文件中用extern声明该外部函数,然后就可以使用,这里的extern也可以省略。 直接在前面声明 void test();j即可
static和extern的总结
1.extern可以用来声明一个全局变量,但是不能用来定义变量
2.默认情况下,一个全局变量是可以供多个源文件共享的,也就说,多个源文件中同名的全局变量都代表着同一个变量
3.如果在定义全局变量的时候加上static关键字,此时static的作用在于限制该全局变量的作用域,只能在定义该全局变量的文件中才能使用,跟其他源文件中的同名变量互不干扰
结构体:
结构体定义的集中方式:
1.先定义结构体类型,再定义变量
1struct Student {
2 char *name;
3 int age;
4 };
5
6struct Student stu;
2.定义结构体类型的同时定义变量
struct Student {
char *name;
int age;
} stu;
3.直接定义结构体类型变量,省略类型名
struct {
char *name;
int age;
} stu;
结构体的注意点:
1、不允许对结构体本身递归定义
结构体中不能定义自身类型的变量
2、结构体内可以包含别的结构体
3.定义结构体类型,只是说明了该类型的组成情况,并没有给它分配存储空间,就像系统不为int类型本身分配空间一样。只有当定义属于结构体类型的变量时,系统才会分配存储空间给该变量
4.结构体变量占用的内存空间是其成员所占内存之和,而且各成员在内存中按定义的顺序依次排列
结构体初始化:
将各成员的初值,按顺序地放在一对大括号{}中,并用逗号分隔,一一对应赋值
struct Student {
2 char *name;
3 int age;
4 };
5
6struct Student stu = {"MJ",27};
结构体的使用
1.一般对结构体变量的操作是以成员为单位进行的,引用的一般形式为:结构体变量名.成员名
stu.age = 27;
2.如果某个成员也是结构体变量,可以连续使用成员运算符"."访问最低一级成员
stu.age = 27;
3.相同类型的结构体变量之间可以进行整体赋值
struct Student stu2 = stu1;
结构体数组:
1、定义方法:
struct Student {
char *name;
int age;
};
struct Student stu[5];//定义1
struct Student {
char *name;
int age;
} stu[5];//定义2
struct {
char *name;
int age;
} stu[5];//定义3
2.初始化
struct {
char *name;
int age;
} stu[2] = { {"MJ",27}, {"JJ",30} };
结构体作为函数参数
将结构体变量作为函数参数进行传递时,其实传递的是全部成员的值,也就是将实参中成员的值一一赋值给对应的形参成员。因此,形参的改变不会影响到实参
1 #include <stdio.h>
2
3// 定义一个结构体
4struct Student {
5 int age;
6 };
7
8void test(struct Student stu) {
9 printf("修改前的形参:%d \n", stu.age);
10 // 修改实参中的age
11 stu.age =10;
12
13 printf("修改后的形参:%d \n", stu.age);
14 }
15
16int main(int argc,constchar * argv[]) {
17
18 struct Student stu = {30};
19 printf("修改前的实参:%d \n", stu.age);
20
21 // 调用test函数
22 test(stu);
23
24
25 printf("修改后的实参:%d \n", stu.age);
26 return0;
27 }
输出结果为:,形参是改变了,但是实参一直没有变过
指向结构体的指针
结构体指针变量的定义形式:struct结构体名称 *指针变量名
有了指向结构体的指针,那么就有3种访问结构体成员的方式
- 结构体变量名.成员名
- (*指针变量名).成员名
- 指针变量名->成员名
1 #include <stdio.h>
2
3int main(int argc,constchar * argv[]) {
4 // 定义一个结构体类型
5 struct Student {
6 char *name;
7 int age;
8 };
9
10 // 定义一个结构体变量
11 struct Student stu = {"MJ",27};
12
13 // 定义一个指向结构体的指针变量
14 struct Student *p;
15
16 // 指向结构体变量stu
17 p = &stu;
18
19 /*
20 这时候可以用3种方式访问结构体的成员
21 */
22 // 方式1:结构体变量名.成员名
23 printf("name=%s, age = %d \n", stu.name, stu.age);
24
25 // 方式2:(*指针变量名).成员名
26 printf("name=%s, age = %d \n", (*p).name, (*p).age);
27
28 // 方式3:指针变量名->成员名
29 printf("name=%s, age = %d \n", p->name, p->age);
30
31 return0;
32 }
输出结果:
typedef的用法:
起别名:
1、给基本书籍类型起别名
typedefint Integer;
Integer i = -10;
2、给指针起别名
typedefchar *String;
String str ="This is a string!";
3、给结构体起别名
// 定义一个结构体
2struct MyPoint {
3 float x;
4 float y;
5 };
// 起别名
8typedefstruct MyPointPoint;
12Point p;
同时结构体起别名的方式还有:
/ 定义一个结构体,顺便起别名
typedefstruct MyPoint {
float x;
float y;
} Point;
甚至可以省略结构体名称:
typedefstruct {
float x;
float y;
} Point;
4、给指向结构体的指针起别名
// 定义一个结构体并起别名
4typedefstruct {
5 float x;
6 float y;
7 }Point;
//起别名
10typedefPoint *PP;
13 // 定义结构体变量
14 Point point = {10,20};
15
16 // 定义指针变量
17 PP p = &point;
18
5、指向枚举类型的指针
1// 定义枚举类型
2enum Season {spring, summer, autumn, winter};
3// 给枚举类型起别名
4typedefenum SeasonSeason;
5
6int main(int argc,constchar * argv[]) {
7 // 定义枚举变量
8 Season s = spring;
9
10 return0;
11 }
其他方法定义:
// 定义枚举类型,并且起别名
typedefenum Season {spring, summer, autumn, winter}Season
甚至可以省略枚举名称,简化为:
typedefenum {spring, summer, autumn, winter}Season;
6、指向函数的指针
1 #include <stdio.h>
2
3// 定义一个sum函数,计算a跟b的和
4int sum(int a,int b) {
5 int c = a + b;
6 printf("%d + %d = %d", a, b, c);
7 return c;
8 }
9
10typedefint (*MySum)(int,int);
11
12int main(int argc,constchar * argv[]) {
13 // 定义一个指向sum函数的指针变量p
14 MySum p = sum;
15
16 // 利用指针变量p调用sum函数
17 (*p)(4,5);
18
19 return0;
20 }
typedef与#define的区别
1typedefchar *String1;
2
3#defineString2char *
4
5int main(int argc,constchar * argv[]) {
6 String1 str1, str2;
7
8 String2 str3, str4;
9 return0;
10 }
第1行给char *起了个别名String1,第2行定义了宏String2。然后在第6、第8行定义了4个变量。
重点来了,注意:在这种情况下,只有str1、str2、str3才是指向char类型的指针变量,str4只是个char类型的变量。