[笔记分享] [Language] C语言进阶小结
来源:互联网 发布:剑灵狂三捏脸数据 编辑:程序博客网 时间:2024/06/05 01:57
1. = 和 ==
=: 赋值运算符; ==: 比较运算符
Exp:
if(a = 3) //errorif(a == 3) //correctif(3 == a) //correct
Note: 提倡 3==a这种写法,如果错写成3=a,编译器会报错。
2. & 和 | 不同于 && 和 ||
| 和&: 按位运算符 &&和||: 逻辑运算符
Exp:
int a = 3, b = 1;c = a | b; // c = 3 0011B | 0001B c = a || b; // c = 1
3. 10、010、0x10和10B
10: 十进制数
010: 第一个0表示八进制数
0x10: 0x表示十六进制数
10B: B表示二进制数
Note: 一般程序中使用二进制和八进制的写法较少。
4. 字符和字符串
不要混淆单引号(‘ ’)和双引号(“ ”),编译器不会检测报错。
单引号引起的一个字符其实表示一个整数,字符用来方便我们读写记忆,相对应的整数都可以在ASCII码表中找到。
Exp: char a =‘0’; // ASCII中的0x30,也可以写成 a = 0x30
Note: 不要把 ‘0’和 ‘\0’混淆。
双引号引起的字符串由字符组成,因此可以有如下两种等效写法,注意在用字符一个个
表示字符串时需要自己添加 ‘\0 ’。
Exp:
char str[] = {‘h’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’}; //写法不方便,用的很少char str[] = {“hello”};
Note: 有的编译器可以直接写成 char str[] = “hello”;
5. 函数指针
Exp:
int (*p)(int a); int fun(int b){ printf(“input value is:%d\n”, b);}p = fun; //fun表示函数指针,所以不带括号和参数哦(*p)(2);
Note: 放在结构体中作为结构体函数指针使用较多,类似于c++中的成员函数。另外一种用的较多的是作为函数参数使用。
Exp:
(*(void(*)())0)(); 在干什么?
其实是将0强制转换成void()()函数指针类型,然后再执行0地址。可以将void()()想象成(int*),那也就是个我们平常看到的类似于(int*)a这样的强制转换。
Note: (*0)()
这样不行,因为运算符*需要一个指针来做操作数,而且还需要是个函数指针,因此要上述0进行转换。
6. 运算符优先级
有些自己不记得的优先级可按照程序的flow添加括号,一来防止出错,二来方便阅读。
Exp:
int register = 1<<3 | 2<<5;
我们可以写成:
int register = (1<<3) | (2<<5); //意图明显,也不会出错
Exp:
*p++ 和 (*p)++ 区别。
这里我们就要区分运算符优先级了, p++是先p++,再 *p, 而(*p)++是先*p再p++。因为和++优先级一样,而且都是自右向左结合。
Note: p++并不是说 *p取的值是p++之后的p,它还是取原来没加过的p。要想取加过后的p的值,那要这么写: (++p)。
7. 注意分号
Exp:
if (a > b); a = b; // 总是能被执行到 和if (a > b) a = b; //条件成立才执行
8. 注意break
Exp:
switch(color) { case 1: printf(“1\n”); case 2: printf( “2\n”); break; case 3: printf( “3\n”); break; default: break;}
由于case 1没有break,当执行完case 1之后,还会执行case 2。
Note: gcc中还可以写成case 2: case3: break;这样,后面会讲到。
9. else问题
Exp:
if (0 == x) if (0 == y) y = 3;else { z = x + y;}
结果违背编程意图,因为else会和最近的if对应,这里也就是if(0 == y)了。
正确写法:
if (0 == x) { if (0 == y) y = 3;} else { z = x + y;}
Note: 一般只有if没else可不加括号,如果有else哪怕if只有一条语句也要加括号,这样可避免出错。
10. 字符串指针
字符串都以 ‘\0’结尾,但不必我们自己写出。如:
char *string = “hello”;
Exp:
char src[] = “hello”;char *dst;strcpy(dst, src); //error,dst没有分配空间
正确写法:
char *dst = (char*)malloc(strlen(src)+1);
Note: 使用malloc记得要检查是否分配成功和使用free()释放内存
11. 作为参数的数组
数组名作为函数参数时会转换成指向该数组第一个元素的指针。因此 int strlen(char s[]) //数组大小传递不了
和 int strlen(char *s)
是等效的。又如 main(int argc, char *argv[])
和 main(int argc, char **argv)
也是等价的,只是强调的意义不同。
12. NULL和NUL
NULL可被定义为 (void*)0,对一个NULL指针进行解引用操作是非法的。如: if (p == NULL) // correct
strcmp(p, NULL) //error
NUL可被定义成 ‘\0’,一般我们用它很少。
13. 边界计算
Exp:
int i;int a[5] = {1,2,3,4,5};for(i=0; i<5; i++) //写成自己喜欢而且方便记忆的风格 printf(“%d\n”, a[i]);
14. 整数溢出和类型转换
Exp:
int a = -34;unsigned int b = 15;if (a > b) a = 10; //会执行这句else a = 15;
Note: 默认的类型转换是从字节少向字节多的转换,有符号的向无符号类型转换。
Exp:
int a = 0xfffffff0;int b = 0x40;if ((a + b) < 0) //结果不可预料 overflow();
正解: If (((unsigned)a + (unsigned)b) > INT_MAX) //INT_MAX代表某个最大整数值
go();
Note: 无符号数做加减运算时不用考虑是否会溢出,但不代表结果是对的。
一般的溢出有数组、数、栈、指针、缓冲区溢出。
15. 声明和定义 int a = 8; //定义并初始化
extern int a; //声明
当一个源文件调用另一个源文件中的定义时,就要用到extern这样的声明了,也适用于函数。
Exp:
//src.cint a = 7;int a = 8; //error 定义了两次,要么报错,要么共享一个实例。或者用static来解决冲突。
Exp:
//src.cint a;int a = 8; //有些编译器不会报错,个人理解是把int a作为声明了。
Exp:
int fun2(void); //当fun2在fun1后面定义而fun1需要调用fun2时需要先声明fun2。int fun1(void){fun2();}int fun2(void){…}
16. 作用域和作用周期
Exp:
static int a; //本文件有效static int fun(int b) //本文件有效{ int a; //覆盖全局a static int c; //本函数有效,而且不会丢失}
17. 形参和实参
Exp:
void swap(int a, int b) //error 企图交换a、b两值{ int c; c = a; a = b; b = c;}
Note: 当函数参数传递时,参数得到的其实只是一个拷贝。
正解:
void swap(int *a, int *b) //error 企图交换a、b两值{ int *c; *c = *a; *a = *b; *b = *c;}
Note: 交换两值的方法有很多,这里只是说明传参的问题。
18. 头文件
Exp:
//file.hextern int a;extern void fun(void);//file.cint a = 3;void fun(void){ …….}
Note: 一般都将源文件中定义想对应的声明都放在一个.h文件中,其他源文件只要包含这个.h文件即可调用,易懂又简洁。
19. 库函数
string.h(strcpy、 strcat、memset、 memmove等) //建议自己动手写下
read、write、open函数
20. const
Exp:
const int a = 2; int const a = 2; //放int前后都一样
Note: const可以用在类型说明符前,也可放在类型说明符后。另外const修饰的变量必须在初始化时赋值。
Exp:
cosnt int *a; //a可变,a指向的值不变int const *a; // a可变,a指向的值不变int *const a; //a不可变,a指向的值可变const int *const a; //都不可变
Note: 一种简单的方法就是看const在*的哪侧。
Note: 一般传递要求为const的参数外用得比较多,如strcpy等。
21. 宏定义
Exp:
#define MAX(a, b) ((a)>(b):?(a):(b))m = MAX(0xf & i, 0xf & j);
Note: a、b两边的括号不能省略,否则展开后错误了。
如果是 MAX(++a, b);这种情况,展开就变成了
((++a)>(b) ? (++a) :( b))
a可能增加一次也可能增加两次。
Note: 尽管函数式宏定义相对真正函数定义有缺点,但是它省去了分配、释放栈帧、传参等工作,效率得到了提高。
Exp:
#define init (x, y) do{x =0; y= 1;}while(0)if (n > 0) init();
如果这里没有do{}while(0),if判断的时候就会出问题。在kernel中很常见这种用法。
22. #、##和可变参数
Exp:
#define STR(s) # sprintf(STR(hello world));
“#”用于创建字符串。#和s之间可以有空格或者Tab, 预处理之后是 printf(“hello world”), 多个空格也变成了一个。
Exp:
#define CONCAT(a, b) a##bCONCAT(con, cat);
“##”用于将前后两个参数连接起来, 所以我们得到的结果是concat。
Exp:
#define show(…) printf(#__VA_ARGS__)show(first, second);
宏定义中也可以用可变参数,用VA_ARGS表示。因此结果是printf(“first, second”);
23. #error使用
Exp:
#ifdef ABC#error aaaaaaaaa fun1();#else fun2();#endif
这里我们不确定fun1()是否有编译进去,可加#error,如果有,编译时就会报错。
24. 宏定义其他用法
Exp:
#ifndef HEADER_FILENAME#define HEADER_FILENAME /* header body */#endif
这样做是为了避免多个源文件在使用.h时会重复包含而出错,一般用.h的文件名作为宏名来防止宏名字冲突。
Exp:
#if MACHINE == ARM int x; #elif MACHINE == MIPS long y; #else #error unkown target machine #endif
用来判断当前处理器是哪种。当然,MACHINE是我们可以用来配置选择的,这样代码就比较灵活,方便移植。
25. 特殊宏
C标准规定了几种特殊的宏,不需要定义即可使用。最常用的有FILE,LINE, 在调试时比较有用。
对于#pragma 这个宏不同的编译器有不同的作用。
26. const和define
Exp:
#define T 10 //存于代码段int main(){ const int t = 10; //存于数据段 int n = t; //运行时确定 int m = T ; //编译时确定}
Note:在头文件中使用#define可以避免头文件重复包含的问题,这点const无法取代。
27. 数组大小
Exp:
#define array_size(arr) sizeof(arr) / sizeof(arr[0]) int array[10]; printf(“%d\n”, array_size(array);
sizeof(arr)中括号可加可不加,和return(1)的括号是一个效果,arr作为左值表示整个数组,而不是作为右值表示指向首元素的指针。因此sizeof(array)为 4*10。sizeof(array[0])的值为4.
Note: sizeof()的结果是size_t类型的,不同的编译平台size_t不同,在stddef.h中定义。
28. typedef用法
Exp:
typedef int INT; typedef int array[10]; typedef void (*fun)(void); typedef struct student { int age; int num; }*pstu;
Note: typedef只是定义一个标识符的别名,是编译过程的一部分,并不实际分配存储空间。
29. define 和 typedef
Exp:
#define INT1 int* typedef (int*) INT2; INT1 a,b; //相当于int *a; int *b; INT2 c,d; //相当于int *a; int b;
Note: 宏定义只是简单的字符串替换,而typedef不是原地扩展
30. Mask
Exp:
int register; register |= 1<<10; register &= ~(1<<8);
Note: 在对硬件的一些寄存器进行操作时,我们经常见到这种写法。
31. 指针
Exp:
int *p = &a[0] ;int *q ;p = q ;int *x = q ;
Note: 指针之间可以互相赋值,也可以用一个指针给另一个指针初始化。另外,* 和 & 互为逆运算。 如果表达式E可以作左值,那么*&E和E是等价的。如果表达式E是个指针,那么&*E和E是等价的。
Exp :
int main(void){ int *p ; *p = 0 ; //p所指向内存不确定,结果不确定。}
Note : 上面的情况就是所谓的“野指针”,因为它指向了不确定的地址。所以记得在使用前一定要初始化,否则出错就很难找到原因。
这里还要提下void*, void*可以转换成任意其他类型指针,任意其他类型指针也可以转换为void 。 我们只能定义void 类型的指针,而不能定义void类型的变量,因为编译器不知道给void类型变量该分配多少空间。同样void*不能直接解引用,而必须先要转换成其他类型的指针再使用。
Note : void*常用作函数传参和返回值。
32. sizeof、strlen
Exp :
int array[10] ;int *p;int len1 = sizeof(array) ; //4x10int len2 = sizeof(p); //4int fun(int arr[]) sizeof(arr) = 4{…..}
Note: sizeof(arr)中因为arr传递的是被数组首地址,所以结果为4.
Exp:
struct student { char sex; int age; int num;}
int len = sizeof(struct student); //len = 12;
Note: 这里主要考虑到一个字节对齐问题。
Exp:
char array[10] = “12345”;int len1 = strlen(array); //len1 = 5;int len2 = sizeof(array); //len2 = 10;
Note: 数组做sizeof的参数不退化,传递给strlen就退化为指针了.
Exp:
char *s = “012345”;sizeof(s); //4sizeof(*s); //1strlen(s); //6
Note: sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
33. 指向数组的指针和多维数组
Exp :
int a[3] = {1, 2, 3} ; int *p ; p = a ; //correct, p = &a是不对的,&a是指向数组的一个指针,而p是指向整型变量的指针 p = &a[0] ; //correct a = p ; //error,数组名是常量,不能作为左值使用。 p++; //指向数组下一个元素,而不是指向下一个内存位置
int *q = p+3;
//q – p之后是等于3,另外注意同类型的指针才能作加减操作
另外,数组名和指针可以互相转换。如a[i] 、p[i]、(p+i)、(a+i)等同,可以推出
i[a]和a[i]也具有同样的含义,不过不推荐这样的写法。
Exp :
int a[12][31] ; int *p = a[0] ; //correct int *p = a ; //error int *p = &a[0][0] ; //correct
这里我们可以把a[12]假想成一个数组名,这样就可以得出:
int (*p)[31] = a ;
数组名和指针的转换以及对其进行加减依然适用于二维数组。可以考虑p++ 、p[0]、(p[0])++、*(a[0] + 3)等是在干什么 , 语法是否有问题?
Note : 一维数组作函数参数可以不写数组大小,二维数组需要写明第二维,如这里的31.
34. 指向指针的指针和指针数组
Exp:
int i;int *p = &i;int **pi = &p;
这样定义后,表达式 *pi取的是p的值,而**pi取的是i的值。
Note: 虽然可以可以有*********p这样无穷的指针,但是我们一般用到**p已经足够了。
Exp:
int *a[10];
表示有数组有10个元素,每个元素是int*指针,这就是指针数组。
int *a[10] 和 int **pa之间的关系有点类似于int a [10]和 int *pa。
Exp :
int *a[10] ;int **pa = a ;int **pb = &a[0]; //和上面是等效的pa++; //结果为a[1], 当然也可以取成pa[1]
Note: 我们经常用的main函数第二个参数就是用到指针数组。
35. malloc 和 free
Exp:
int fun(void){ char *p = malloc(10); //编译器对其返回值(void*)做了隐式转换。 if (p == NULL) error(); ….}
这里只使用malloc分配了内存,但是使用完之后却没有释放,造成了内存泄露。
Note: malloc之后一定要检查内存是否分配成功。malloc和free要成对使用,避免内存泄露。另外,malloc返回的指针一定要保存好,如果该指针丢失,就没办法free这块内存了。free之后p里面所存储的值还是不变的。
一些特殊情况: malloc(0)调用也是合法,会返回一个非NULL的指针,也可以通过free释放,但是不能通过值这个指针访问内存。 free(NULL)也是合法的,不做任何事情。
36. 指向指针的指针作为参数
Exp:
const char *msg[] = {“sunday”, “monday”} ;void get_day(const char **p){ *p = msg[0]; }char *day = NULL;get_day(&day); printf(“%s\n”, day); //结果为”sunday”
Note: 另外一种特别用法是在函数中分配内存,调用者通过传参取得指向该内存的指针。
37. 与零值比较
布尔: if (value) //为真时
if (!value) //为假时
Note: 在c中bool类型用不到。
整型: if (value == 0)
if (value != 0)
浮点型: if ( x == 0.0) //error, 浮点数都有精度限制
if (( x > -VALUE) && (x <= VALUE)) //correct
Note: 无论是float还是double类型变量,都有精度限制,所以要避免将浮点变量用”==”或”!=”与数字比较。
指针: if (p == NULL)
if (p != NULL)
38. volatile
是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
GNU C
39. 零长度数组
Exp:
struct data{ int len; char data[0];}
data[0]不占空间,只是用了data[index]之后,我们可以访问放在len之后的数据,这里假设data的数据域保存在struct data之后的内存区域。如:
struct data dat;for(i=0; i<dat.len; i++){ printf(“%c\n”, dat.data[i]) ;}
40. case范围
支持case x….y这样的语法,如:
switch (ch){ case ‘0’… ‘9’: c -= ‘0’; break; case ‘a’... ‘z’: c -= ‘a’- ‘0’; break;}
41. typeof
typeof(x) 可以获得x的类型。这样在传参时,我们不需要传入变量的类型。类模板还要传参呢!!
42. 标号元素
标准C要求数组或者结构体的初始化必须以固定的顺序出现,在GNU C中, 通过制定索引或结构体成员名,允许初始化以任意顺序出现。
Exp:
struct file_operations fops = {.read = my_read,.write = my_write}
- [笔记分享] [Language] C语言进阶小结
- 《C语言进阶》学习笔记
- C Language笔记
- c语言学习笔记分享之函数
- c语言学习笔记之数组小结
- The C Programming Language 笔记
- 《The C Programming Language》笔记
- C Language Reference - C 语言参考 - HackerJLY
- C语言进阶_笔记 第二章 预处理
- C语言进阶_笔记 第四章 数组
- C语言进阶
- C语言进阶
- C语言进阶
- C语言进阶3
- C语言学习进阶
- C语言进阶
- C语言进阶
- C语言进阶:串口
- JAVA 定时激活程序代码
- RNTI
- poj1011:Sticks
- VPC使用SNAT实现内网机器可以使用互联网
- 你好:MineData
- [笔记分享] [Language] C语言进阶小结
- [AS尝龟]Unable to inflate view tag without class attribute
- leveldb-编码
- C++各种变量内存分配
- WOJ1022-Competition of Programming
- java编程思想之控制执行流程
- java RSA和AES加密解密工具
- Android原生webView加载h5页面出现加载错乱不完全问题
- JSON和JSONP