[笔记分享] [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标准规定了几种特殊的宏,不需要定义即可使用。最常用的有FILELINE, 在调试时比较有用。
对于#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] = {123} ;    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}