c语言重要知识点总结

1 关键字

1.1 sizeof

sizeof是关键字,不是函数

1.2 static

static两个作用:限制作用域(本文件),延长生命周期

  • 静态全局变量:作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没法使用他。
  • 静态局部变量:由于被static修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态变量的值还是不会被销毁,函数下次使用时仍然能用到这个值

静态函数:作用域仅限于本文件

1.3 基本数据类型

float 4byte, double 8byte

1.4 变量命名规则
  • 所有宏定义、枚举常数、只读变量全用大写字母命名,用下划线分割单词
  • 定义变量的同时千万千万别忘了初始化。定义变量时编译器并不一定清空了这块内存,它的值可能是无效的数据
1.5 signed

编译器缺省默认情况下数据为 signed 类型

1.6 switch case

case后面只能是整型或字符型的常量或常量表达式(想想字符型数据在内存里是怎么存的)

1.7 if else

在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数

1.8 void

void*指针,任何类型的指针都可以直接赋值给它,无需进行强制类型转换,“空类型”包含“有类型”

1.9 const
  • const修饰的只读变量必须在定义的同时初始化
  • 先忽略类型名(编译器解析的时候也是忽略类型名),我们看const离哪个近。“近水楼台先得月”,离谁近就修饰谁。
    const int *p; // const 修饰*p, p是指针,*p是指针指向的对象,不可变
    int const *p; //const 修饰*p,p 是指针, *p 是指针指向的对象,不可变
    int *const p; /const 修饰 p, p 不可变, p 指向的对象可变
    const int *const p; //前一个 const 修饰“, 后一个 const 修饰 p,指针 p 和 p 指向的对象都不可变
1.10 union

在 union中所有的数据成员共用一个空间,同一时间只能储存其中一个数据成员,所有的数据成员具有相同的起始地址

2 符号

  • 逻辑运算符||的条件只要有一个为真,其结果就为真;只要有一个结果为假,其结果就为假
  • 左移和右移:当为正数时, 最高位补 0;而为负数时,符号位为 1,最高位是补 0 或是补 1 取决于编译系统的规定。 Tu rbo C 和很多系统规定为补 1
  • 左移和右移的位数不能大于数据长度,不能小于0
  • “ +”号的优先级比移位运算符的优先级高
2.1 运算优先级

1 []高于*,比如int *a[];
2 函数()高于*,比如 int *fp();
3 ==和!=高于位运算,比如val & mask != 0;
4 ==和!=高于赋值运算符,比如c = getchar() != EOF
5 算术运算符高于赋值运算符,比如 msb << 4 + lsb

3 预处理

另外 ANS I 标准 C 还定义了如下几个宏:
_LINE_ 表示正在编译的文件的行号
_FILE_ 表示正在编译的文件的名字
_DATE_ 表示编译时刻的日期字符串,例如: “25 Dec 2007 ”
_TIME_ 表示编译时刻的时间字符串,例如: “12:30:55”
_STDC_ 判断该文件是不是定义成标准 C 程序

3.1 宏定义

反斜杠作为接续符时,在本行其后面不能再有任何字符,空格都不行
宏的生命周期从#define开始到#undef结束
#include <filename> 先到规定系统路径去寻找 include "filename" 先到当前目录去寻找

3.2 #pragma

待总结

3.3 内存对齐

Char 偏移量必须为sizeof(char)即1的倍数
int 偏移量必须为sizeof(int)即4的倍数
float 偏移量必须为sizeof(float)即4的倍数
double 偏移量必须为sizeof(double)即8的倍数
Short 偏移量必须为sizeof(short)即2的倍数
例:下面这个结构体中sizeof(MyStruct)为8+4+4=16

struct MyStruct 
{ 
double dda1; 
char dda; 
int type 
}; 

可以好好摆放结构体中元素的位置以充分利用内存
使用指令#pragma pack(n),编译器将按照 n 个字节对齐,对于特定item,对其的基准为min(n,sizeof(item))
使用指令#pragma pack(),编译器将取消自定义字节对齐方式。

3.4 #运算符
#define SQR(x) printf("The square of x is %d.\n,((x)*(x))");

运行SQR(8)输出 The square of x is 64

#define SQR(x) printf("The square of #x is %d.\n,((x)*(x))");

运行SQR(8)输出 The square of 8 is 64

3.5 ##运算符
#define XNAME(n) x##n

XNAME()会被展开成x8.##就是个粘合剂,将前后两部分粘合起来

4 指针和数组

4.1 野指针
int *p;
*p = NULL;

注意,NULL定义#define NULL 0,但是NULL和0表示的意思完全不一样

4.2 省政府与市政府区别

注意sizeof(a)=sizeof(int)*5,因为a代表的是数组的整块内存
&a是数组首地址(省政府),&a[0]是数组首元素首地址(市政府)

4.3 数组名作为左值和右值的区别

对于x=y,左值:x代表的地址,右值:y所代表的地址里面的内容
对于数组,int a[5]
- a**作为右值,代表数组首元素的首地址**,所以才可以用a[i]访问数组内的元素。
- a**不能做左值!!!**

4.4 a和&a的区别

&a是数组a的首地址a地址一样,但是意思不一样

int a[5] = …;  //省略具体数值
*(a+1)  //a是数组首元素的首地址,所以+1之后是下一个元素的地址
 &a+1  //指向的是下一个数组 &a+5*sizeof(int),即`a[5]`,已经越界
int *ptr = (int *)(&a+1) // 将上一步计算出来的地址强制转换成int*类型
*(ptr-1)  //指向`a[4]`
4.4 指针数组和数组指针
  • 指针数组:存储指针的数组,int* p[10]
  • 数组指针:指向数组的指针,int (*p)[10]
4.5 地址强制转换
struct Test{
...
}*p;

假设p的值是0x100000,那么

p + 0x1 = 0x100000 + sizeof(Test)  //与数组的指针变量加减整数一样,整数的单位是元素的个数
(unsigned long)p + 0x1 = 0x100001   //整数与整数相加
(unsigned int*)p + 0x1 = 0x100004    //0x100000 + (unsigned int)*0x1
int a[4] = {...};
int *ptr = (int*)((int)a+1);  //元素a[0]的第二个字节开始的连续4byte的地址
4.6 二维指针
int a[5][5];
int (*p)[4];
p=a;

分析一下p[4][2]: &p[0]+4*4*sizeof(int)+2*sizeof(int)

4,7 数组参数与指针参数
  • 一维数组作为函数参数的时候,编译器总是把它解析下好呢给一个指向其首元素首地址的指针
  • main函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局变量一样长。全局变量一定是在函数外部定义的。
  • 指针作为参数的时候,同样传递的是拷贝的那份,并不是指针本身
void getmemory(char *p, int num){
    p = (char*)malloc(sizeof(char)*num);
}
int main(){
    char * str = NULL;
    getmemory(str,10);
}

上面的程序中malloc的内存的地址并没有复制给str,而是给了_str。解决办法:
(1)用return

void getmemory(char *p, int num){
    p = (char*)malloc(sizeof(char)*num);
    return p;
}
int main(){
    char * str = NULL;
    str = getmemory(str,10);
}

(2)二级指针:因为要操作的是指针,所以传入指针的地址,即二级指针

void getmemory(char **p, int num){
    *p = (char*)malloc(sizeof(char)*num);
}
int main(){
    char * str = NULL;
    getmemory(&str,10);
}
4.8 函数指针
char* fun(char* p1,char* p2){
……
}
void main(){
    char *(*pf)(char* p1,char* p2);
    pf = &fun;或者 pf = fun; //函数名被编译之后其实就是一个地址,所以两种方法没有本质区别
    (*pf)("aa","bb");
}
void func(){
...
}
void main(){
    void (*p)();
   *(int*)&p = (int)func;  //将函数的入口地址赋值给指针变量
   //(int*)&p将地址强制转换成指向int类型数据的指针
   //(int)func将函数的入口地址强制转换成int类型数据
   //*(int*)&加锁,转向,解锁
}
char* (*pf[3])(char *p); //函数指针数组,数组名是pf
char*  (*(*_pf)[3])(char *p); //函数指针数组的指针,_pf是指针,相当于用(*_pf)替换pf

5 内存管理

5.1 野指针

指针变量除了在使用时,别的时间都把针拴到“0”地址处,即

  • 定义指针变量的同时最好初始化为NULL
  • 用完指针之后也将指针变量设置为NULL
5.2 结构体指针未初始化
struct student{
    char* name;   //只分配了四个字节,name并没有指向一个合法的地址,这时候其内部存的只是一些乱码
    ...
}stu;
void main(){
    strcpy(stu.name,"Jimy");
    ...
}

解决方法:为name malloc一块内存空间

5.3 数组内存的初始化
int a[10]={0};
memset(a,0,sizeof(a));
5.4 malloc函数

原型(void *)malloc(int size),由于返回值是void *,所以需要进行强制转化,比如

char *p = (char*)malloc(100);

使用malloc函数需要注意,如果所申请的内存块大于目前堆上剩余的整块内存块,则内存分配失败,函数返回NULL。注意,这里说的“剩余的整块内存块”不是所有剩余内存块之和,因为malloc函数申请的是连续的一块内存。所以,malloc函数必须用if(NULL != p)来验证内存分配是否成功。

猜你喜欢

转载自blog.csdn.net/qsdzxp/article/details/82152942