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)
来验证内存分配是否成功。