一、c基础
1.1 一个函数遇到return语句就终止了
1.2 system系统调用:用命令打开计算器、记事本等,windows和linux下命令不同,需要头文件(stdlib.h)
1.3 gcc -o a a.c :-o为指定输出程序后缀,如果不加-o,gcc a.c windows下生成a.exe,linux下生成a.out
1.4 编译过程
1.5 -E预编译:gcc -E -o a.e a.c 预编译a.c文件,生成的目标文件名为a.e,预编译实际是把头文件解析,把include包含的文件替换,同时删除注释;
1.6 -S反汇编:gcc -S -o a.s a.e 把预编译后的a.e文件编译成汇编语言文件a.s
1.7 -c编译:将代码编译成二进制的机器指令
1.8 gcc没有任何参数,代表就是链接
1.9 用户模式:应用程序都是运行在用户区域;内核模式:操作系统的内核,设备驱动程序在内核模式下运行
1.10 RISC:精简指令集 CISC:复杂指令,一般x86构架的CPU都是复杂指令集
二、数据类型与运算符
2.1 #define MAX 10 定义一个宏常量,值为10,define定义的常量类型要大写
2.2 %x:代表输出16进制整数,%u用大写字母方式输出16进制数 ,%o:代表输出八进制整数,%p:代表输出内存的地址,%c代表输出一个字符
2.3 short:短整数,在32位系统下是两个字节,16个比特;
long:长整数,在32为系统下是4个字节,64为系统下windows还是4个字节,unix是8个字节;
int:不管32,64位或windows、unix都是四个字节
long long:是64位,8个字节,对于32位的操作系统,cpu寄存器是32位,所以计算long long类型的数据,效率很低
unsigned int:unsigned是关键字,代表无符号数的意思
2.4 计算一个整数的时候超过整数能够容纳的最大单位后,整数会溢出,溢出的结果是舍弃最高位
当一个小的整数赋值给一个大的整数,符号位不会丢失,会继承
2.5 对于arm、intel这种x86的构架的复杂指令cpu,整数在内存中是倒着存放的,低地址放低位,高地址放高位,叫小端对齐,
对于unix服务器的cpu,更多的是采用大端对齐方式存放整数
2.6 char的本质就是一个整数,一个只有1个字节大小的整数
2.7 char c; c=c+32; //将大写字母转化为小写字母的算法
c=c-'0'; //将字符转化为整数的算法
2.8 不可打印的char转义符:\a:报警;\b:退格;\n:换行;\r:回车;\t:制表符;\\:斜杠
2.9 char取值范围为-128到127,unsigned char取值范围为0到255
2.10 float在32位系统下是4个字节,double在32位下是8个字节
2.11 当一个浮点数赋值给一个整数时,小数点后的位数会被舍弃,如果想要四舍五入,可以把浮点数+0.5
2.12 volatile:告诉编译器不要优化
register int i;变量i不是在内存里面,而是在寄存器里面
2.13 qutchar是输出一个字符的函数
2.14 getchar();得到用户键盘的按键,一次只能接收一个,跟scanf相似
2.15 一个字节有8个bit
三.数组、字符串
3.1 数组在内存中是一段连续的空间,每个元素的类型都是一样的
3.2 如果scanf能容纳的长度小于用户在键盘输入的长度,那么scanf就会缓冲区溢出,导致程序崩溃
3.3 gets();认为回车是输入结束的标识,空格不是输入结束标识,scanf();认为空格和回车键都是输入结束标识;都存在缓冲区溢出问题。
——gets();不能用类似%s,%d之类的转义字符,只能接受字符串的输入
3.4 atoi(); 将字符串转换为int类型;atof();将字符串转化为float类型;atol();将字符串转化为long类型,需要头文件<stdlib.h>
3.5 fgets(s , 100 , stdin); 第一个参数是char数组,第二个参数是大小,单位:字节;第三个参数stdin代表标准输入;
——fgets是安全的,只要保证第二个参数小于等于数组的大小,就不存在缓冲区溢出,如果有溢出自动截取
3.6 puts(s);输出字符串,会在输出完成后打印回车
3.7 strlen(s); 得到字符串长度,返回不包含字符串结尾/0的字符串长度
3.8 strcat(s, s1);往字符串s后面追加s1字符串;也存在缓冲区溢出问题,要尽量保证s字符串容量
3.9 strncat(s , s1 ,6);往字符串s后面追加字符串s1的前6个字符
3.10 strcmp(s1,s2);比较s1和s2是否相等,如果返回值为0,代表两个字符串中内容相等
3.11 strncmp(s1,s2,5);比较s1和s2的前面5个字符是否相等,如果相等返回0
3.12 strcpy(s1,s2);将s2的内容拷贝到s1里面并覆盖s1
3.13 strncpy(s1,s2, 3);将s2的前三个字符拷贝到s1,并覆盖s1前三个字符
3.14 sprintf(s,"%d",i);将格式化(此例用%d格式化)后的字符串输出第一个参数指定的字符串中,print是将格式化结果输出到屏幕,sprintf是将格式化结果输出到字符串
3.15 sscanf();scanf是 ,sscanf是从指定格式化字符串读取输入
——char s[100]="abc=100";
——int i=0;
——sscanf(s, "abc=%d" , &i);
——printf("%d\n",i); 输出结果为100
3.16 char * strchr(char* _Str , int _Ch);在参数_str中查找参数_ch指定字符,找到返回_ch在_str中所在位置,没找到返回null;
3.17 char * strstr(char * _str , const char * _Substr);在参数_str中查找参数_Substr指定子串,找到返回子串在_str中位置,没有找到返回null
3.18 strtok(s , “_”);字符串s中遇到_就分割成子串,需用循环将分割的字符串打印出来
——strcpy(s , "abc_123");
——buf = strtok(s , "_");
——while(buf)
——{
—— printf("%s\n",buf);
—— buf=strtok(NULL,"_");
——}
四.函数
1.如果一个函数没有标明返回值,那么返回的函数类型就是int
2 size_t 是一种变量类型:unsigned int;有些函数如果确定返回值不会小于0,就可以使用size_t
3 形参必须加数据类型;
4 要在main函数中调用一个函数,需要先声明函数;
五.指针
1.指针变量的值,一般不能直接赋值一个整数,而是通过取变量地址的方式赋值
2.*p是指针指向地址的内容
3. p是指针指向地址的内存地址
4. void *p;//无类型指针,意思是这只是一个指针变量,而不指向任何具体的数据类型
5. &取地址运算符,可以取得一个变量在内存中的地址
6. NULL在c语言中的定义为(void *)0
7. 指向NULL的指针叫空指针,没有指向任何变量地址的指针叫野指针
8. 只要是指针,就是固定大小,32位下4个字节,64位下8个字节
9. 指针之间赋值比普通类型检查严格,原则上不可把double *类型赋值给int*类型,不能把一种类型的指针指向另一种类型,如有指针指向其他类型,从低地址开始取值
10. 一个变量在内存中存放时变量前面的值在内存高地址,后面的值在内存低地址,指针指向从低地址开始,字符数组相同,指针首先指向数组的首端,与其他变量一样从低地址开始指向(具体可看c基础 指针与数组的关系)
11.不能把int型的指针指向float
12.const int *p 这个指针只能指向一个常量,例:int a=10;p可指向a,但不能改a的值,p也可以指向其他常量,区别 int const *p
13.int *const p 常量指针,一旦初始化后就不可以改变指向,但可以更改所指常量的值
14.*P指针与数组对应,如果遍历数组时一定要注意数组长度与p大小对应,不能让p指空
15. 指针的运算是指针指向的数据类型在内存中占用字节数做为倍数加减,
——char *p ;p++;移动了sizeof(char) 个字节,一个字节
——int *p;p++;移动了sizeof(int)个字节,四个字节
16. int buf[10];int *p1=&buf[1]; int *p2=&buf[3];
—— p2 - p1 =2; p1和p2都是int型相减得到的是两个数组元素在内存中的距离(8),因为是int型的(4个字节),所以结果为2
——char *p1=&buf[1]; char *p2= &buf[3] ; p1和p2是char型相减得到的也是两个数组元素在内存中的距离(8),因为是char型的(1个字节),所以结果为8
——long long *p1=&buf[1] ; long long *p2=&buf[3] ; p1和p2是long long型相减得到的也是两个数组元素在内存中的距离(8),因为是long long型的(8个字节),所以结果为1
——short *p1=&buf[1]; short *p2= &buf[3] ; p1和p2是short型相减得到的也是两个数组元素在内存中的距离(8),因为是short型的(1个字节),所以结果为4
—— (int)p1 - (int)p2=8; 类型转化后相减得到内存中两个指针的距离,int字节为4,相减为8
——(int)p1+3; 加了int类型后加3是指针在内存地址的加法,单纯的加3,假如原先内存为:320680,加3后为320683
——p1+=3;不加类型是指针指向的数据类型在内存中的加法,p1为int型,加3后指针在内存中移动了12个字节
17.关闭安全检查:#pragma warning (disable:4700);
18.下面例子说明当一个指针指向一个整数后,从低地址位开始取,指针的运算让指针指向整数的不同位置
int main(){ int a = 0x1234; char *p = &a; char *p1 = p++; printf("%x\n", *p); //34 printf("%x\n", *p1); //12 return 0; }19.指针指向数组时,也可以这样写:
int main(){ int buf[10] = { 1,3,56,7,7,8,4,5,4,6}; int *p = buf; int *p1 = buf + 2; printf("%d\n", *p); //1 printf("%d\n", *p1); //56 return 0; }20.下面两种指针写法效果一样:
int main(){ int buf[10] = { 1,3,56,7,7,8,4,5,4,6}; char *p = &buf; printf("%d,%d,%d,%d\n", *p,*(p+1),*(p+2),*(p+3)); printf("%d,%d,%d,%d\n", p, p[1], p[2], p[3]); //c语言允许指针通过数组下标的方法访问数组成员 return 0; }21.注意p[ n ] 修改的是谁的值
int *p = buf[1]; P++; P++; p[2] = 100; //经过两轮++后p已经指向buf[3],此时p[2]是在p指向buf[3]的基础上+2,因此指向了buf[5],修改的是buf[5]的值
22. 定义指针数组不管什么类型的,占用字节大小都相等,int *a[10];char *b[10];
——sizeof(a)=40,sizeof(b)=40,都等于40,sizeof(a[1])=4,sizeof(b[1])=4
23.二级指针
int main(){ int a = 10; int *p = &a; int **pp = &p; //定义了一个二级指针,指向一个一级指针 **pp = 100; //通过二级指针修改内存的值,将a改成了100 *pp = 10; //相当于将p指向了编号为10的这块内存,pp还是正常的指针,但p被修改成了野指针 printf("%d\n", a); //100 return 0; }24.多级指针指向原理
25.多级指针对应内存图解:***ppp代表了int a所在的内存快,**pp代表了int *p所在的内存快,*ppp代表了int **pp所在的内存块,ppp代表了***ppp所在的内存块
26.指向二维数组的指针
a |
二维数组名称,数组首地址 |
a[0], *(a + 0), *a |
0行,0列元素地址 |
a + 1 |
第1行首地址 |
a[1], *(a + 1) |
第1行,0列元素地址 |
a[1] + 2, *(a + 1) + 2, &a[1][2] |
第1行,2列元素地址 |
*(a[1] + 2), *(*(a + 1) + 2), a[1][2] |
第1行,2列元素的值 |
int main(){ int buf[2][3] = { {1,2,3},{4,5,6} }; //int *p[3]; //指针数组 int (*p)[3]; //定义了一个指针,指向int[3]这种数据类型,指向二维数组的指针 p = buf; //p指向了二维数组中的第一行 p++; //指向了第二行,在内存中位移了1*sizeof(int [3])=12个字节 int i; int j; for(i=0;i<2;i++){ for(j=0;j<3;j++){ printf("%d\n", *(*(p + i) + j)); printf("%d\n", p[i][j]); //两种写法相同,都能输出相同的结果 } } return 0; }28.指针变量作为函数的参数:当一个实参传递给一个函数作为形参用时,这个函数不会改变实参的值,但是如果这个函数的形参接收的是指针类型,函数就会改变形参的值
void change(int a, int b) { int temp = a; a = b; b = temp; } void s_change(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } int main(){ int a = 10; int b = 20; change(a, b); printf("%d,%d\n", a, b); //输出值为10,20,调用change函数并未改变a,b的值 s_change(&a, &b); printf("%d,%d\n", a, b); //输出值为20,10,值改变,调用函数时传递的是地址,传递时用& return 0; }
void array(int *buf){ printf("%d\n", sizeof(buf)); //输出结果为4 } int main(){ int buf[10] = {}; array(buf); printf("%d\n", sizeof(buf)); //输出结果为40 return 0; }30.一维数组名作为函数参数,这个函数的第一个参数是数组名,第二个参数可以是数组的维度,告诉这个函数这个数组长度
31.将二维数组作为参数传递给一个函数:
void two_level(int (*p)[3],int a,int b) //第一个参数为二维数组,第二三个参数为数组维度
{ int i; int j; for(i=0;i<a;i++){ for(j=0;j<b;j++){ printf("p[%d][%d]=%d\n", i, j, p[i][j]); } } }
32.当数组作为参数传递不知道数组维度时,一维数组可以用:sizeof(buf)/sizeof(int)得到维度,二维数组可以用:sizeof(buf)/sizeof(buf[0])得到第一个维度,sizeof(buf[0])/sizeof(int)得到第二个维度
33.二维数组作为参数时可以不指定第一个参数,所以也可以写成:
void two_level(int [][3],int a,int b){};与这种方式等效:
void two_level(int (*p)[3],int a,int b){};34.一维数组作为参数传递写法有:int buf[];int buf;int *buf;效果相同
35.数组作为函数的参数,当不需要改变数组的内容时,可以加const修饰,const int buf;
36.void *p(int , char *); //声明了一个函数,函数的名字叫p,函数的返回值是void *,函数的参数分别是:int 和char *
void (*p)(int ,char *); //定义了一个指向参数为int和char *,返回值为void的函数指针
int *(*p)(int*); //定义了一个参数为int *,返回值为int *的指向函数的指针
void(*p)(); //定义一个指向没有参数,没有返回值的指向函数的指针