比较数字大小

第一种:

int main()
{int num1;int num2;scanf("%d%d",&num1, &num2);if(num1>num2)printf("较大值是:%d\n",num1);else if(num1 == num2)printf("两个数值一样大");elseprintf("较大值是:%d\n",num2);return 0;

第二种:

int MAX(int x,int y)//max是个整型
{if (x >= y)return x;elsereturn y;
}//再去定义max函数
int main()
{int num1;int num2;int max;scanf("%d%d",&num1, &num2);max = MAX(num1,num2);//先设置一个函数maxprintf("max = %d\n",max);return 0;
}

波浪符~ 对一个数二进制按位取反

int main()
{int a = 0;//4个字节 32个bit//在内存中占用4个字节,空间取名为aint b= ~a;//按(二进制)位取反 b是有符号的整型//00000000000000000000000000000000(0的二进制)//11111111111111111111111111111111(取反)//10000000000000000000000000000000(符号位=第一个不变)//10000000000000000000000000000001(反码+1得到补码)
/原码 反码 补码
//原码符号位不变,其余取反得到反码,反码+1得到补码
//负数在内存中存储的时候存储的是二进制的补码
printf("%d\n",b);//使用打印的是这个数的原码
return 0;}

单目运算符

前置后置+±-操作符

int main()
{int a = 10; //a++整体就是a的值 a++最后运算//int b = a++;//后置++ 先把a赋值a++,a再自增 b=a++=10 a=a+1=11//int b = ++a;//前置--,先++,再使用 a=a+1=11 b=a=11//int b = a--;//后置--,先使用,再-- b=a=10 a=a-1=9//int b = --a;//前置--,先--,再使用 a=a-1=9 b=a=9printf("a = %d b = %d\n",a,b);//连续输出时,a的值是根据当前空间a的值为初始值return 0;

强制转换符

int main()
{int a = (int)3.14;//括号里放类型表示强制转换 //这里强制把3.14从double类型转换成int整型printf("%d\n",a);//得到3return 0;}

逻辑操作符(与或非)

int main()
{//非零的数为真//零为假//&& - 逻辑与//|| - 逻辑或//! - 逻辑非 (0变1 ,1变0)int a = 3;int b = 5;//int c = a && b;//a=3为真,b=5为真,左右同时为真,逻辑与为真(并且)//全1得1 有0则0//int c = a||b;//a或b只要有一个真就是真,有1则1,全0则0//智能运算,逻辑与:表达式有不成立,则不再往后执行//智能运算,逻辑或:表达式成立,则不再往后执行printf("c = %d\n",c);、return 0;
int a=10,b=20,c=30;int d = a<b && 0 && a++ && b++ || a-- || b-- && c++ || a-b && c--//与智能运算有不成立,不在往后执行 ,只再逻辑与的范围内,逻辑或同理//分析:只执行了一个a--
a=9,b=20,c=30,d=1

条件(三目)操作符

int main()
{int a = 10;int b = 20;int max;max = (a>b?a:b); //a>b吗,真输出a,假输出bif(a>b)max = a;elsemax = b;printf("%d\n",max);return 0;
}
//条件操作符//exp1?exp2:exp3; 有女朋友?虐狗:被喂狗粮;
int main()
{int a;int b;scanf("%d",&a);b = (a > 5 ? 3 : -3);printf("%d\n",b);return 0;}

逗号表达式

int main()
{int a = 1;int b = 2;int c = (a>b,a=b+10,a,b=a+1);//逗号表达式 c = b =13  a= 12//逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。//最后一个值赋值给cprintf("c = %d\n",c);return 0;
}

下标操作符

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};//定义数组printf("%d",arr[4]);//[] - 下标引用操作,数组通过下标来访问,下标从0开始return 0;//arr和4是两个操作数,就像3+5中,+是操作符,3和5是操作数
}

函数调用操作符

int ADD(int x,int y)//定义函数(指定函数名,参数,返回类型,函数体等)
{int z = 0;z= x + y;return z;
}int main()
{int a =10;int b =20;int sum = ADD(a,b);//() - 函数调用操作符printf("%d\n",sum);return 0;
}

typedef类型重定义

int main()
{typedef unsigned int u_int;//typedf - 类型重定义u_int num2 = 20;return 0;
}
//typedef 与 define的区别:*> #define是宏定义,后面没有分号,而typedef是一条语句,后面有分号*> #define只是最简单的替换,而typedef是实实在在的取别名

auto 自动存储类型

*>auto        -----   自动存储类型1>姿势:auto 类型 变量名;例:auto int a;       -----   自动在栈中占用4个字节空间auto可以省略,顾: int a;2>作用域:从定义开始,到所在的代码块结束3>生命周期:从定义开始,到函数结束4>作用对象:局部变量5>初始值:垃圾值

register寄存器存储类型

*>register    -----   寄存器存储类型1>姿势:register 类型 变量名;例:register int a;  -----   将a变量在寄存器中申请空间2>作用域:从定义开始,到所在的代码块结束3>生命周期:从定义开始,到函数结束4>作用对象:局部变量5>初始值:垃圾值6>作用提升性能

static修饰变量(局部和全局)

void test()//void一般用于没有返回值的函数,和int同地位
{static int a =1;//a为静态局部变量,static修饰局部变量延长代码周期//在静态区申请空间a++;//static修饰全局变量改变变量作用域,只能在所在的源文件内部使用//实则改变的是连接属性,将外部属性改变为内部属性printf("a = %d\n",a);
}
int main()
{int i = 0;while(i<5){test();i++;}printf("i = %d\n",i);return 0;
}
----------------------------------------------------------------------*>static   -----   静态存储类型1>姿势:static 类型 变量名;例:static int a;   -----   在静态区域申请空间2>作用域:*>修饰局部变量:从定义开始,到所在代码块结束*>修身全局变量:从定义开始,到文件末尾3>生命周期:从被定义开始,到整个程序结束4>作用对象:*>作用局部变量,变成静态变量,只初始化(定义)一次*>作用全局变量,该变量只在当前文件中有效,不能被外部引用*>作用函数,该函数只在当前文件中有效,不能被外部引用5>初始值:0或者NULL

extern 声明外部函数

int Add(int x,int y)//定义函数
{int z;z = x + y;return z;
}
extern int Add(int,int);//声明外部函数  ,extern 只能引入变量无法定义变量 ,改变引入变量则改变的是全局变量
int main()
{int a = 10;int b = 20;int sum = Add(a,b);printf("sum = %d\n",sum);return 0;
}
//gcc编译器下,gcc main.c Add.c
//extern 作用域若是局部变量,
//              是全局变量
//              作用函数

#define 定义标识符常量

#define MAX 100//定义一个标识符常量,还可以定义宏-带参数
int main()
{int a = MAX;printf("a = %d\n",a);return 0;
}

宏定义和函数定义

int Max(int x, int y)//函数定义MAx
{if(x>y)return x;elsereturn y;
}#define MAX(X,Y) (X>Y?X:Y)//宏定义MAX,宏定义仅仅替换,不做运算
int main()
{int a = 10;int b = 20;//函数定义int max = Max(a, b);printf("max = %d\n",max);//宏定义max = MAX(a, b);//max = (a>b?a:b)printf("max = %d\n",max);return 0;
}

指针变量

int main()
{int a = 10;//向内存申请4个字节的空间int*p = &a;//取出变量a的地址,使用&取地址操作符,将地址赋值给pprintf("%d\n",&a);printf("%d\n",p);*p = 20;//* - 解引用操作符(对p进行解引用操作,找到所指对象a)//此为找到a,把a改为20printf("a = %d\n",a);//有一种变量是用来存放地址的 -指针变量//我通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量//printf("%p\n",&a);return 0;}

结构体

struct Book
{char name[20];//C语言程序设计short price;//50
};int main()
{//利用结构体类型创建一个该类型的结构体变量struct  Book b1 = {"C语言程序设计",50};struct Book* pb = &b1;//利用pb打印书名和价格printf("%s\n",pb->name);//箭头操作符 结构体指针->成员printf("%d\n",pb->price);//printf("%s\n",(*pb).name);指针//printf("%d元\n",(*pb).price);printf("书名:%s\n",b1.name);//点操作符 结构体变量.成员
printf("价格:%d元\n",b1.price);return 0;

if选择语句

#include <stdio.h>
int main()
{int age =0;scanf("%d",&age);if(age<18)printf("未成年\n");else if (age>=18&&age<28)//&&表示并且printf("青年\n");else if (age>=28&&age<50)printf("壮年\n");else if (age>=50&&age<90)printf("老年\n");elseprintf("仙人模式\n");return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o5wf2zU1-1660146373135)(C:\Users\TATDistance\AppData\Roaming\Typora\typora-user-images\image-20220718094822227.png)]

左移操作

int main()
{int a = 10;printf("%d\n",a<<2);//将a中的10取出,放入cpu寄存器中,再进行移位操作//00000000 00000000 00000000 00010100  左移两格相当于 10*2^2=40printf("%d\n",a);return 0;
}//如果是负数子左移,符号位也会被吞掉,空出来补0

右移操作

int main()
{int a = 10;printf("%d\n",a<<2);//00000000 00000000 00000000 00010100  int b = -10;printf("%d\n",b>>2);//原码 反码 补码 得到11111111 11111111 11111111 11110110//逻辑右移:0011111111 11111111 11111111 111101 空补0//算术右移:1111111111 11111111 11111111 111101 空的地方补符号位return 0;
}

位操作

按位与

int main()
{int a = 18;//00000000 00000000 00000000 00010010int b = 23;//00000000 00000000 00000000 00010111printf("c = %d\n",a&b);00000000 00000000 00000000 00010010return 0;//c = 18,按位与,位置上都为1,结果才为1
}

按位或

int main()
{int a = 26;//00000000 00000000 00000000 00011010int b = 23;//00000000 00000000 00000000 00010111printf("c = %d\n",a|b);00000000 00000000 00000000 00001111return 0;//c = 8,按位或,位置上只要有1,结果就为1
}

按位异或

int main()
{int a = 26;//00000000 00000000 00000000 00011010int b = 23;//00000000 00000000 00000000 00010111printf("c = %d\n",a^b);00000000 00000000 00000000 00001101return 0;//c = 31,按位异或,位置上只要数据不一样,结果就为1
}

switch语句

int main()
{int day = 0;scanf("%d",&day);switch(day)//switch判断括号内数字是几,从case几进入,括号内必须是整型{case 1://决定入口printf("星期1\n");break;//跳出case 2://case 后 跟整型常量表达式  不能带小数 不能是变量printf("星期2\n");//switch(整形表达式){语句项} break;case 3:printf("星期3\n");break;case 4:printf("星期4\n");break;case 5:printf("星期5\n");break;case 6:printf("星期6\n");break;case 7:printf("星期7\n");break;}return 0;
}
int main()
{int day = 0;//一定要是整型scanf("%d",&day);switch(day)//switch判断括号内数字是几,从case几进入,括号内必须是整型{case 1://决定入口case 2:  //可以去掉重复的语句达到想要的效果case 3:   case 4: case 5:printf("工作日\n");break;case 6:  case 7:printf("休息日\n");break;default://处理逻辑外的情况情况//default放哪都行,一般放最后printf("输入错误\n");break;}return 0;
}
int main()
{int n = 1;int m = 2;switch(n){case 1:m++;//m = 3case 2:n++;//n = 2case 3:switch(n)//switch允许嵌套使用,此时从n = 2进入{case 1:n++;case 2:m++;n++;//n = 3.m = 4break;//跳出内层}case 4:m++;//n = 3,m = 5break;//跳出外层default:break;}printf("m = %d,n = %d\n",m,n);//得到答案m=5 n=3return 0;
}

while循环中break 和 continue作用

int main()
{int i = 0;while(i<=10){i++;//在循环内自增可避免死循环if(i == 5)//break;永久终止循环continue;//终止本次循环,直接跳转while语句判断部分,进行下一次循环printf("%d ",i);}return 0;}
int main(){int ret = 0;
int ch = 0;
char password[20] = {0};
printf("请输入密码:>");
scanf("%s",password);//scanf读取空格和回车前的字符串
while((ch=getchar())!='\n')//让scanf一直读取,直到读取到回车键,保证全部读取完,做一个死循环
{;
}
printf("请确认(Y/N):>");
ret =  getchar();
if(ret == 'Y')
{printf("确认成功\n");
}
else
{printf("确认失败\n");
}
return 0;
}
int main()
{int ch = 0;while((ch=getchar())!=EOF)//EOF = end of file文件结尾{if(ch<'0'||ch>'9')//只输出数字字符continue;//直接跳转while语句判断部分,进行下一次循环putchar(ch);//效果等于 printf"%d\n,ch"; 接收的是ascill值}return 0;
}
int main()
{int i = 1;//循环变量的初始化while(i<=10)//循环变量的判断{if(i == 5)continue;printf("%d",i);i++;//循环变量的调整}return 0;
}

for循环语句

int main()
{int i = 0;for(i=1;i<=10;i++)//for(初始化;判断;调整){if(i==5)break;//结果到5为止,结束循环continue;//结果没有5,结束这次循环printf("%d",i);}return 0;}
int main()
{for(;;)//for循环的初始化,调整,判断都可以省略,初始化不要轻易省略。//但for循环的判断部分被省略,则判断条件恒为真{printf("hehe\n");}return 0;}

do…while()循环语句

int main()
{int i =1;do //最少执行一次{printf("%d",i);i++;}while(i<=10);//回去循环继续打印直至10,到11不循环不打印结束return 0;
}
int main()
{int i =1;do //最少执行一次{if(i==5)//break;跳出循环continue;//跳过本次打印printf("%d",i);i++;}while(i<=10);//回去循环继续打印直至10,到11不循环不打印结束return 0;
}

goto语句

int main()
{printf("helloworld\n");
goto again;//强行跳转到again//一般跳出多层嵌套
printf("你好\n");again:printf("呵呵\n");return 0;
}

strcpy 函数 - stringcopy- 字符串拷贝 ;strlen -string length -字符串长度

int main()
{char arr1[]="bit";char arr2[20]="############";strcpy(arr2,arr1);printf("%s\n",arr2);//strcpy - string copy - 字符串拷贝//strlen - string length - 字符串长度return 0;
}

main函数

main函数有两种姿势
1-int main(void)
2-int main(int argc,char *argv[])* argc : 代表 用户  输入参数  的 个数* argv :指针数组,保存了输入的所有信息

函数定义和使用场景

int get_max(int x,int y)//定义函数
{if (x>y)return x;elsereturn y;
}
int main()
{int a = 10;int b = 20;int max = get_max(a,b);//函数的使用场景,函数的名字就是函数的首地址printf("max = %d\n",max);max = get_max(100,300);printf("max = %d\n",max);return 0;
}

函数和指针

void Swap2(int *pa,int* pb)//定义函数 void无返回值  pa pb是形参
{int tmp = 0;tmp = *pa;*pa = *pb;//* 解引用 找到a*pb = tmp;
}
int main()
{int a =10;//当实参传给形参的时候,形参其实是实参的一份临时拷贝//对形参的修改是不会改变实参的,实质就是判断传值还是传地址int b =20;Swap2(&a,&b);//调用函数printf("a= %d b = %d\n",a,b);return 0;
}
//传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。
//传地址调用:把变量的内存地址传递给函数参数,可以让函数和变量建立真正的联系,函数内部可以直接操作函数外部的变量
int binary_search(int arr[], int k, int sz)//本质上这里的arr是个指针
{//算法的实现,一次二分查找不够,得放进while循环了多次查找int left = 0;int right = sz-1;while(left<=right)//左边小于右边才能进循环{int mid = (left+right)/2;//中间元素的下标if (arr[mid]<k){left = mid+1;}else if (arr[mid]>k){right=mid-1;}else{return mid;}}return -1;//不满足左边小于右边或者查找不到 就跳出循环 返回-1}
int main()
{//二分查找,在一个有序数组中查找具体的某个数int arr[] = {1,2,3,4,5,6,7,8,9,10};int k = 7;int sz = sizeof(arr)/sizeof (arr[0]);int ret = binary_search(arr,k,sz);//确定这个功能要得到什么样效果if(ret == -1)//传递过去的是数组arr首元素的地址{printf("找不到指定的数字\n");}else{printf("找到了,下标是:%d\n",ret);}return 0;
}

写一个函数每调用一次,num加一

void Add(int* p)
{(*p)++;
}
int main()
{int num = 0;Add(&num);printf("num = %d\n",num);Add(&num);printf("num = %d\n",num);Add(&num);printf("num = %d\n",num);return 0;
}

链式访问

int main()
{//1 分开int len = 0;len = strlen("abc");printf("%d\n",len);//2.链式访问printf("%d\n",strlen("abc"));return 0;
}
int main()
{printf("%d",printf("%d",printf("%d",43)));//返回u的是字符数 先打43 然后2个字符 打印2 然后是1个字符 打印1//结果就是4321return 0;
}

函数的声明和定义

1、声明在函数使用之前,先声明后使用,一般放在头文件中。

2、告诉编译器一个函数叫什么,参数是什么,返回类型是声明,不考察是不是存在。

3、函数定义是指函数的具体实现,交代函数的功能实现。

函数递归

1、函数调用自身的一种方法,目的是大事化小,减少代码量。

2、函数调用自己再栈区占用空间,会致使栈溢出。

看练习题

函数循环

看练习题

数组

同一类型数据保存在一块连续的空间,作用是保存数据

创建数组的注意事项

创建方式 type_t arr_name [const_n];

//type_t 指的是数组的元素类型

//const_n 是一个常量表达式,用来指定数组的大小

一旦数组被定义,只能通过下标方式存入数据 int arr[100]就是定义数组0

int main()
{//int arr[10] = {1,2,3};//创建一个数组-存放整形10个//char arr2[5] = {'a',98};//不完全初始化,剩下的元素默认初始化为0,字符数组是字符串 "'1',''\0'"//int arr[20] = {0}; ----清0//char arr3[5] = "ab"; char arr4[] = "abcdef";printf("%d\n",sizeof(arr4));//sizeof 计算arr4所占空间的大小 7个元素charprintf("%d\n",strlen(arr4));//strlen 计算字符串长度 \0不算 6个字符//int n = 5;//char ch[n];//错误,[]要为常量表达式return 0;
}

一维数组的使用

一维数组
int arr[5];  ----> 可以存储5个int类型数据arr[0]=1;   ---->   *(arr+0)=1;1[arr]=2;   ---->   *(1+arr)=2;arr :当数组来看:  int [5]
arr : 当首地址看:  第一个元素的地址: int *如果你要使用arr里面的值,那么就作为地址使用int *p = arr;
int main()
{char arr[] = "abcdef";//[a][b][c]][d][e][f][\0]//printf("%c\n",arr[3]);//%c 字符串类型,arr[3]打印数组下标为3的元素即为dint i = 0;//int len = strlen(arr);//或者初始化for(i=0;i<(int)strlen(arr);i++)//f的下标是5 ,也可以用strlen求数组的长度{printf("%c ",arr[i]);}return 0;}
int main()
{int i =0;int arr[] = {1,2,3,4,5,6,7,8,9,10};int sz = sizeof(arr)/sizeof(arr[0]);//求出数组元素个数for (i=0;i<sz;i++){printf("%d ",arr[i]);}return 0;
}
1、数组是使用下标来访问的,下标从0开始。例如int arr[10];2、数组的大小可以通过计算获得。如int sz = sizeof(arr)/sizeof(arr[0]);
--------------------------------------------------------
int main()
{int arr[] = {1,2,3,4,5,6,7,8,9,10};int sz = sizeof(arr)/sizeof(arr[0]);int i = 0;for (i=0;i<sz;i++){printf("&arr[%d] = %p\n",i,&arr[i]);//%p打印地址,数组是连续存放的}return 0 ;
}
结果是://每个地址差一个数据类型,元素上差一
&arr[0] = 00F5F8D4
&arr[1] = 00F5F8D8
&arr[2] = 00F5F8DC
&arr[3] = 00F5F8E0
&arr[4] = 00F5F8E4
&arr[5] = 00F5F8E8
&arr[6] = 00F5F8EC
&arr[7] = 00F5F8F0
&arr[8] = 00F5F8F4
&arr[9] = 00F5F8F8

二维数组

二维数组的使用

变量名[一维数组个数[每一个一维数组中元素个数]

int arr[m][n];//m个int[n]类型数据
int main()
{int arr[3][4] = {{1,2,3},{4,5}};//三行四列的二维数组//一行放满了再放第二行,多的空位放0//可用大括号选择元素放一行//char arr[5][6];//创建二维数组,前面为行后面为列//行可以省略,列不可以省略int i = 0;for(i=0;i<3;i++){int j = 0;for(j=0;j<4;j++){printf("%d ",arr[i][j]);}printf("\n");}return 0;
}

冒泡函数

void bubble_sort(int arr[],int sz)//无返回类型用void//这里的arr是通过数组传参上来的是arr首元素的地址
{//确定冒泡排序的趟数int i = 0;//int sz = sizeof(arr)/sizeof(arr[0]);//首元素地址的大小只能为4/8,看系统是32位还是64位for(i=0;i<sz-1;i++){int flag = 1;//假设这一趟要排序的数据已经有序//每一趟冒泡排序的内容int j = 0;for(j=0;j<sz-1-i;j++){//两两交换位置if(arr[j]>arr[j+1]){int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;flag = 0;//本躺排序的数据其实不完全有序}}if (flag == 1)//如果数组已经有序,跳出循环{break;//break只能用于for和switch,因此这里的break是跳出开头的循环语句}}
}
int main()
{int arr[] ={9,8,7,6,5,4,3,2,1,0};//创建一个数组int i = 0;//初始化int sz = sizeof(arr)/sizeof(arr[0]);//得到数组元素个数 ,这里的arr是整个数组,arr[0]是数组的第一个元素//对arr进行排序,排成升序//arr是数组,我们对数组arr进行传参,实际上传递过去的是数组arr首元素地址arr[0]bubble_sort(arr,sz);//冒泡排序函数for(i=0;i<sz;i++){printf("%d ",arr[i]);}return 0;
}//数组名的三种情况:
//1、数组首元素地址-arr[0]的地址(除了2、3种情况,其余情况均为第一种,即数组首元素地址  //2、sizeof(数组名)-整个数组的大小(字节),数组名代表整个函数//3、&数组名 - 对整个数组取地址,即得到整个数组的地址,数组地址与数组首元素地址结果一样,其内涵不一样。也是数组指针(数组的指针),例如int*[4]=&a,而指针数组(数组里的元素都是指针类型)
int cmp_int(const void* e1, const void* e2);
void bubble_sort(void* base, int sz, int width,int(*cmp)(void* e1,void* e2));
struct Stu
{char name[20];int age;
};
int cmp_int(const void* e1, const void* e2)
{//比较两个整型值//void* 类型的指针,可以接收任意类型的地址//void* 类型的指针,不能进行解引用操作//void* 类型的指针,不能进行+-整数的操作return *(int*)e1  - *(int*)e2;//1-2<0 返回<0负数  1-2>0 返回>0正数}
int cmp_stu_by_age(const void* e1,const void* e2)
{return ((struct Stu*)e1) ->age  -  ((struct Stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1,const void* e2)
{//比较名字就是比较字符串//字符串比较不能直接用大于小于,应该用strcmp函数return strcmp(((struct Stu*)e1) ->name ,((struct Stu*)e2)->name);
}
void Swap(char* buf1,char* buf2,int width)
{   int i = 0;for(i=0;i<width;i++)//每个字节每个字节的交换{char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}
void bubble_sort(void* base, int sz, int width,int(*cmp)(void* e1,void* e2))
{int i = 0;for(i=0;i<sz-1;i++)//趟数{int j = 0;//每一趟比较的对数for(j=0;j<sz-1-i;j++){//两个元素的比较if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)//放两个参数,参数里是两个相邻元素的地址,但是void*类型不能做加减,只能强转,//所以cmp(base,(char)*base+width),但下个循环就是第2,3个元素比较,//前一个参数也要强转使他能够加减得到cmp((char*)base+j*width,(char)*base+(j+1)*width)//用j*width,根据循环次数跳过j个宽度{//交换元素Swap((char*)base+j*width,(char*)base+(j+1)*width,width);}}      }
}
void test4()//整型数组
{int arr[10] =  {9,8,7,6,5,4,3,2,1,0};int sz = sizeof(arr)/sizeof(arr[0]);                                           bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
}
void test5()//结构体数组
{struct Stu s[3] = {{"zhangsan",20},{"lisi",40},{"wangwu",10}};//结构体数组int sz = sizeof(s)/sizeof(s[0]);bubble_sort(s,sz,sizeof(s[0]),cmp_stu_by_age);
}
int main()
{//test1();//test2();//test3();test4();test5();return 0;
}

三子棋 程序设计

代码看 vs2010 test_7_9

指针

指针变量 我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个 变量就是指针变量

int main()
{int a = 10;//在内存中开辟一块空间int*p = &a;//对变量a取出地址,使用&取地址符号,//存放地址的变量叫指针变量//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节(小的)的地址存放在p变量中,p就是一个指针变量return 0;
  • 指针是用来存放地址的,地址是唯一标示一块地址空间的。

  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

    int main()
    {int a = 0x11223344;int*pa =&a;//*pa = 0;char* pc = &a;//char 字符串的地址是首元素的地址*pc = &a;//指针类型决定了指针进行解引用操作,能访问空间的大小//printf("%p\n",pa);//printf("%p\n",pc);return 0;
    }
    
  • 指针类型决定了指针在进行解引用操作的时候,能够访问空间的大小,也就是有多大的权限(能操作几个字节)

比如:int*p; *p能访问4个字节

char*p; *p能访问1个字节

double*p; *p能访问8个字节

int main()
{int a = 0x11223344;int*pa = &a;char*pc =&a;printf("%p\n",pa);printf("%p\n",pa+1);//走了4个字节,跳过一个整型printf("%p\n",pc);printf("%p\n",pc+1);//走了1个字节,跳过一个字符型return 0;}
  • 指针的类型决定了指针向前或者向后走一步有多大(距离)。
int main()
{int arr[10] = {0};int* p = arr;//数组名-首元素地址int i = 0;for (i=0;i<10;i++)//如果是int*则一次改4个字节,char*则1个字节1个字节的改{*(p+1) = 1;}return 0;
}

野指针

int main()
{//int a;//局部变量不初始化,默认是随机值int *p;//局部的指针变量,就被初始化随机值*p[20];return 0;
}
int main()
{int arr[10] = {0};int *p = arr;int i = 0;for(i = 0;i<12;i++)//越界访问{*(p++) = i;//当指针指向的范围超出数组arr的范围,//p就是野指针}return 0;
}
int* test()
{int a = 10;//a是局部变量,return 返回后变量销毁return &a;
}
int main()
{int*p = test();//只接收了地址,但不能通过地址改变内容printf("%d\n",*p);return 0;
}

如何规避野指针

  1. 指针初始化 2. 小心指针越界 3. 指针指向空间释放,及时设置为NULL
    4. 避免返回局部变量的地址 5. 指针使用之前检查有效性
int main()
{int b =0;int a =10;int*pa = &a;//初始化int*p =NULL;//NULL-用来初始化指针的,给指针赋值return 0;}

指针±整数

i

nt main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int i = 0;int sz = sizeof(arr)/sizeof(arr[0]);int* p = &arr[9];for (i=0;i<5;i++){printf("%d ",*p);//p++; //*p++  等于 *p再p++ //p = p+1;p-=2;}return 0;
}

指针减指针

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d",&arr[9] - &arr[0]);// 9,指针-指针=中间元素个数(绝对值)
printf("%d",&arr[9] - &ch[0]);//指针要指向同一块空间才能减
return 0;
}

指针和数组

int main()
{int arr[10] = {0};printf("%p\n",arr);//数组的首元素地址printf("%p\n",arr+1);+4bitprintf("%p\n",&arr[0]);//数组的首元素地址printf("%p\n",&arr[0]+1);+4bitprintf("%p\n",&arr);//printf("%p\n",&arr+1);+40bit //1.&arr  &数组名 取 整个数组的地址//2.sizeof(数组名) 计算整个数组的大小return 0;
}

二级指针

int main()
{int a  =10;//int 类型,int* pa = &a;//一级指针,a的地址存放在pa中 int*类型int* * ppa = &pa;//二级指针,pa的地址存放在ppa中 int**类型**ppa = 20;printf("%d\n",**ppa);//20printf("%d\n",a);//20return 0 ;//最右边的* 代表为指针类型//左边是指向的对象 pa是int*类型的  }

指针数组

指针数组-存放指针的数组

int main()
{int a = 10;int b = 20;int c = 30;int* pa  =&a;int* pb = &b;int* pc = &c;//整型数组 - 存放整形//字符数组 - 存放字符//指针数组 - 存放指针//int arr[10]; int* arr2[3] = {&a,&b,&c};//指针数组int i = 0;for (i=0;i<3;i++){printf("%d\n",*(arr2[i]));//解引用 指针数组的地址 就找到指针的值}//arr2[1]等于 &a ,加个* ,等于解引用a的地址,等于10return 0;
}

进制

二进制:逢二进一

八进制:逢八进一 标志:0开头

十进制:逢十进一 整数默认十进制

十六进制:逢十六进一 标志: 0x 开头

进制转换

十进制转二进制:(除以2,逆向取余)

十进制转八进制:(除以8,逆向取余)

十进制转十六进制:(除以16,逆向取余)

二进制转十进制:(权位相加)

八进制转十进制:(权位相加)

十六进制转十进制:(权位相加)

10进制小数

整数部分 除以进制数,逆向取余

小数部分 乘以进制数,正向取整

数据在内存中的储存

整型在内存中都是以补码的形式存在,加减乘除都是以补码的形式计算 原码是给人分析数据的码 你先拿到补码,在从符号位判断正负,在人为转为原码,就可以拿来分析 只有整型才有补码反码原码

short类型 值范围:-32768 ----32767

01111111 11111111 ----32767 最大值 加一为最小值、

10000000 00000000 ----- -32768

double xx = -1314520.25 在内存中的储存形式

0001 0100 0000 1110 1101 1000 —1314520

0.01—0.25

1 0100 0000 1110 1101 1000.25 = 1. 0100 0000 1110 1101 100001*2^20

符号位 1(1位)

指数位 01111111111 +19 (16+2+1)=10000010010 (11位)

小数位 0100 0000 1110 1101 100001… (52位)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KE8l4O0U-1660146373136)(C:\Users\TATDistance\AppData\Roaming\Typora\typora-user-images\image-20220719093459595.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3RpsIOwh-1660146373136)(C:\Users\TATDistance\AppData\Roaming\Typora\typora-user-images\image-20220719093614308.png)]

数据类型输出方式

%c 字符形式打印
%d int类型
%hd short类型
% hhd char类型10进制
%ld long 类型
%lld longlong类型
%s 字符串
%x 16进制打印 (不带标志位)%o  8进制打印(不带标志位)
%#x16进制打印 (带标志位)%#o  8进制打印(带标志位)
%f float 类型(保留6位小数)
%lf double 类型(保留6位小数)
%g float double (保留有效)
%u unsigned int类型
%lu unsigned long类型
%llu unsigned longlong类型
%hu unsigned short类型
%hhu unsigned char类型
%md   m是所占的空间数
%m.nf   m是宽度(空间数)包含小数点 n是小数的位数
//例如 int a = 1234  m<4,无效果,m>4,右对齐,m<-4 左对齐
//float f = 12.56789  m的绝对值小于实际空间数,则无效,保留小数会四舍五入

结构体

结构是一些值的集合,被称为成员变量

//描述一个学生 名字,年龄,电话,性别
//struct Stu//struct 结构体关键字  Stu - 结构体标签  整体是结构体类型
//{ //成员变量
//  char name[20];
//  short age;
//  char tele[12];
//  char sex[5];
//}s1,s2,s3;//三个全局的结构体变量//typedef 对类型重命名
typedef struct Stu//struct 结构体关键字  Stu - 结构体标签  整体是结构体类型
{   //成员变量char name[20];short age;char tele[12];char sex[5];
}Stu;//Stu是重命名后的类型,分号不能丢,
//因为语句、定义不需要分号结尾,声明需要分号结尾。
int main()
{Stu s1 = {"吴庚享",22,"17620062069","男"};//结构体初始化Stu s2 = {"张三",22,"11111111111","男"};return 0;}
struct S
{int a;char c;char arr[20];double d;
};struct T
{char ch[10];struct S s;char *pc;
};int main()
{char arr[] = "hello bit\n";struct T t = {"hehe",{100,'w',"hello world",3.14},arr};//结构体嵌套printf("%s\n",t.ch);//hehe// 通过点操作符(.)结构体成员的访问printf("%s\n",t.s.arr);//hello worldprintf("%lf\n",t.s.d);//3.140000printf("%s\n",t.pc);//hello bitreturn 0;
}

结构体传参时,要传结构体的地址

typedef struct Stu//struct 结构体关键字  Stu - 结构体标签  整体是结构体类型
{   //成员变量char name[20];short age;char tele[12];char sex[5];
}Stu;//Stu是重命名后的类型,分号不能丢void Print1(Stu s)
{printf("name = %s\n",s.name);//通过点操作符(.)结构体成员的访问printf("age: %d\n",s.age);printf("tel = %s\n",s.tele);printf("sex = %s\n",s.sex);
}
void Print2(Stu* ps)//生成4或8字节的指针变量
{printf("name: %s\n",ps->name);//指针访问结构体成员要用"->"操作符printf("age: %d\n",ps->age);printf("tel: %s\n",ps->tele);printf("sex: %s\n",ps->sex);
}
int main()
{Stu s = {"张三",20,"10010","男"};Print1(s);//Print2(&s);//传地址return 0;
}

数据的存储

类型的意义:

1.告诉了程序员这个类型开辟内存空间的大小(大小决定了使用范围)

2.看待内存空间的视角

指针类型的意义:

1.指针类型决定了指针解引用操作符能访问几个字节 char *p ; *访问1个字节

2.指针类型决定了指针加一或者减一时到底加的几个字节char *p; p+1 跳过一个字符也就是一个字节,如果是int *p; p+1 跳过一个整型四个字节

int check_sys();
int main()
{//写完一段代码告诉我们机器当前的字节序int ret = check_sys();//返回1为小端 返回0为大端if (ret == 1){printf("小端\n");}else{printf("大端\n");}return 0;
}
int check_sys()
{int a = 1;char *p = (char*) &a;//取a的地址强制转换成char类型放进p指针变量中return *p;//返回1小端 返回0大端
}

指针进阶

1.字符指针

int main()
{char ch = 'w';char* pc = &ch;//字符指针是个变量用来存放地址*pc = 'w';return 0;}
-------------------------------------------------------------------
int main()
{char arr[] = "abcdef";char* pc = arr;//字符指针存放数组名,数组首元素地址printf("%s\n",arr);printf("%s\n",pc);//字符串打印地址,pc就是地址不需要解引用return 0;}
-------------------------------------------------------------------
int main()
{const char* p = "abcdef";//"abcdef"是常量字符串,常量字符串不能被改变printf("%c\n",*p);//*p从首地址取出指针指向的'a',打印字符aprintf("%s\n",p);//这里是指针p,指向字符串首元素地址,根据地址找到整个字符串return 0;
}
int main()
{char arr1[] = "abcdef";char arr2[] = "abcdef";const char *p1 = "abcdef";const char *p2 = "abcdef";if (arr1 == arr2){printf("hehe\n");}else{printf("haha\n");//打印haha,因为arr1和arr2代表数组首元素地址,数组存储地址不一样}-------------------------------------------------------------  if (p1 == p2){printf("hehe\n");//打印hehe,因为"abcedef"是常量字符串,p1,p2都指向同一块空间的起始位置地址}else{printf("haha\n");}return 0;
}

2.数组指针

数组指针是能够指向数组的指针!

int main()
{int *p = NULL;//p是整型指针 - 指向整型的指针 -- 可以存放整型的地址char *pc = NULL;//pc是字符指针 - 指向字符的指针 -- 可以存放字符的地址//数组指针 - 指向数组的指针 -- 可以存放数组的地址//int arr[10] = {0};//arr - 首元素地址; &arr[0] - 首元素地址 ; &arr - 数组的地址int arr[10] = {1,2,3,4,5,6,7,8,9,10};//int* p[10] ;//&arr整个数组的地址,这是指针数组,[]优先级比*高,存的是数组int (*p)[10] = &arr;/1/这是数组指针,加了括号,*p是指针,指向数组10个元素,每个元素的数据类型是整型,存的是地址return 0;
}
int main()
{int arr[10] =  {1,2,3,4,5,6,7,8,9,10};int(*pa)[10] = &arr;int i = 0;for (i=0;i<10;i++){printf("%d",(*pa)[i]);//第一种用法,一般不这么用}------------------------------------------------for (i=0;i<10;i++){printf("%d",*(*pa+i));//*pa 是数组名,首元素地址,加i向后跳i个元素,*pa+i就是每个元素的地址,再解引用得到元素}//第二种用法return 0;
}
-----------------------------------------------------int i = 0;int *p = arr;for (i=0;i<10;i++){printf("%d",*(p+i));//p指向第一个元素地址,+i跳过i个元素,(p+i)是每个元素的地址,解引用找到元素}//这种方法更加常用和易于阅读

数组指针一般用在二维数组上才方便

首先是懂得怎么打印一个二维数组:

void print1(int arre[3][5], int x, int y);//声明,int arre[3][5]是数组的形式
void print2(int (*p)[5],int x,int y);//int (*p)[5]就是数组指针,指针的形式
int main()
{int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};print1(arr,3,5);//调用,传过去一个arr数组return 0;
}
void print1(int arr[3][5], int x, int y)//定义
{int i, j =0;for (i =0; i < x;i++)//for循环嵌套(第一次循环行数){for(j = 0;j < y;j++)//(第二次循环列数)第一行5列//2行5列,3行5列这样打印完{printf("%d",arr[i][j]);}printf("\n");}
}
----------------------------------------------------void print2(int (*p)[5],int x,int y)
{int i =0;for (i=0;i<x;i++){int j =0;for (j=0;j<y;j++){printf("%d",*(*(p+i)+j));//p指向一行,p+i跳过i行//p+i得到整个数组的地址,对数组地址解引用得到数组名,*(p+i)则对应数组首行的地址//*(p+i)+j是这一行里的具体某个元素的地址,对整体作解引用就得到i行j列的元素//一维数组中arr[i]=*(arr+i)=*(p+i)=p[i]//同理二维数组中arr[i][j]=*(*(p+i)+j) =(*(p+i))[j]=*(p[i]+j)=p[i][j]}printf("\n");}
}
int arr[5];//arr是5个元素的整型数组
int *parr1[10];//parr1是一个指针数组,数组有10个元素,每个元素的类型是int*
int (*parr2)[10];//parr2是一个数组指针,指针指向的是有10个元素的数组,元素的类型是int类型
int (*parr3[10])[5];//parr3先跟[10]结合,是一个数组,除了parr3[10]外,其余的是类型。
//数组元素类型是除数组名和方括号以外的东西组成,指针类型是除名字以外的东西组成
//该数组有10个元素,每个元素是个数组指针,该数组指针指向的数组有5个元素,每个元素是int类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUUMksr9-1660146373138)(D:\C语言笔记\图库\二维数组中 指针数组 数组指针解读.png)]

3.指针数组

指针数组是一个存放指针的数组!

int arr[10] = {0};//整形数组
char ch[5] = {0};//字符数组
int* arr1[10];//整形指针的数组 --指针数组
char *arr2[4];//存放一级字符指针的数组--指针数组
char **arr3[5];//二级字符指针的数组
int main()
{int arr1[] = {1,2,3,4,5};int arr2[] = {2,3,4,5,6};int arr3[] = {3,4,5,6,7};int* parr[] = {arr1,arr2,arr3};//数组放三个元素,这三个元素分别是上面三个数组的首元素地址int i = 0;for (i=0;i<3;i++){int j = 0;for (j=0;j<5;j++){printf("%d",*(parr[i]+j));//parr[i]找到数组每个元素,每个元素是数组名,数组名又是每个数组的首元素地址//指针加j,找到每个元素地址}printf("\n");}return 0;}

4.数组传参和指针传参

一维数组传参

void test(int arr[])//ok,传数组,拿数组在参数部分接收
{}
void test(int arr[10])//ok,数组里不填也行,随便填不影响
{}
void test(int *arr)//ok,传数组名,等于传数组首元素地址,拿指针接收没问题
{}
void test2(int *arr[20])//ok
{}
void test2(int **arr[])//ok,数组名是首元素地址,int*是数组元素类型,第二个*表示指针,一级指针的地址放到二级指针
{}
int main()
{int arr[10] = {0};int *arr2[20] = {0};test(arr);test2(arr2);return 0;
}

二维数组传参

void test(int arr[3][5]){}//ok,行可以省略,列不可以省略
void test(int *arr){}//不行,整型指针是用来存放整型的地址
void test(int* arr[5]){}//不行,arr[5]是数组,数组5个元素,每个元素是int*类型,这是指针数组
void test(int (*arr)[5]){}//可以,数组指针指向5个元素的数组,类型是int
void test(int **arr){}//不行,二级指针用来存放一级指针的地址int main()
{int arr[3][5] ={0};test(arr);//二维数组传参,传数组的地址return 0;
}

一级指针传参

void test1(int *p){};void test2(char* p){};
int main()
{int a =10;int* p1 = &a;test1(&a);//传变量的地址test1(p1);//传一级指针char ch = 'w';char* pc = &ch;test2(&ch);//传一个字符变量的地址test2(pc);//传一个字符类型指针return 0;}

二级指针传参

void test(int **p){}
//参数部分是二级指针,接收一级指针的地址或者二级指针
int main()
{int *ptr;int** pp = &ptr;test(&ptr);//可以传一个一级指针地址test(pp);//可以传一个二级指针本身int* arr[10];//数组有10个元素,每个元素是int*类型test(arr);//可以传一个存放一级指针的数组的数组名,数组名表示首元素地址//就是等于接收一级指针的地址return 0;}

5.函数指针

数组指针–指向数组的指针

函数指针–指向函数的指针,函数指针存放函数的地址

int Add(int x,int y)
{int z = 0;z = x + y;return z;
}
int main()
{int a = 10;int b = 20;//int arr[10] = {0};printf("%d\n",Add(a,b));//printf("%p\n",&Add);//printf("%p\n",Add);//&函数名和函数名都是函数的地址int(*pa)(int,int) = Add;//pa是函数指针printf("%d",(*pa)(2,3));//函数指针存放函数的地址return 0;}
void Print(char*str)//接收一个char*类型的字符串地址
{printf("%s\n",str);//%s打印需要输入一个地址,数组名就是首元素地址
}
int main()
{void(*p)(char*) = Print;//存函数名到p里,p是指针,类型是(*)(char*)//前一个括号是代表p是指针,后一个括号代表指向的类型是char*,返回类型是void(*p)("hello bit");//调用函数,*p找到函数,函数名后跟()表示调用return 0;
}
看懂这四个代码,就懂函数指针了
//代码1
(*((void(*)())0)())
//void(*p)(int,int) 是无返回值的函数指针类型
//((void(*p)(int,int))把一个类型括住,是强制类型转换,
//(void(*p)(int,int)1231)放在0前是把0强制类型转换,而0是int类型,把0强制转换为无返回值的函数指针类型,函数指针存放函数的地址所以这一步是想要把0当成某函数的地址
//(*((void(*)())0)())对一个地址解引用,找到这个函数,函数参数是无参,函数类型是void类型的
//总结:本质是调用0地址处的该函数//代码2
void(*signal(int,void(*)(int)))(int);
//1.signal是一个函数
//2.signal(int,void(*)(int))函数里的两个参数,第一个是int类型,第二个void(*)(int)是函数指针类型,该函数指针指向函数的参数是int,返回类型是void,
//3.void(*signal(int,void(*)(int))) 函数的返回类型是void,可类比int Add(int,int)
//4.void(*                 )(int);函数的返回类型也是一个函数指针该函数指针指向函数的参数是int,返回类型是void,
//5.void(*signal(int,void(*)(int)))(int);函数返回类型也是一个函数指针
//不按规定写:
//返回类型:void(*)(int) 函数:signal(int,void(*)(int))void(*signal(int,void(*)(int)))(int);
简化方法:
typedf void(*pfun_t)(int);//把 void(*)(int)重命名pfun_t
pfun_t signel(int,pfun_t);//参数里放类型//代码3
函数指针 :void (*(*)(int (*)(void)))(short)
typedef (int (*)(void)) fun1;
// 函数指针指向的函数的参数是无参类型,该指向的函数的返回值是int类型
typedef void (*)(short)  fun2 ;
// 函数指针指向的函数的参数是short类型,该指向的函数的返回值是void类型
重命名后 fun2 (*)(fun1) //函数指针,函数的参数是函数指针类型,返回类型也是函数指针类型//代码4
int (*(*arr[10])(int))(int,short)
typedef int (*)(int,short) fun1
//函数指针指向的函数的两个参数分别是int,short类型,该指针指向的函数的返回值是int类型
typedef  fun1(*)(int)  fun2
//函数指针指向的函数的参数分别是int类型,该指针指向的函数的返回值是函数指针类型类型。也就是 int (*)(int,short)类型
重命名后 fun2 arr[10]
//函数指针指向一个函数,该函数的返回类型是函数指针类型
//arr[10]中每个元素的类型是函数指针类型

6.函数指针数组

函数指针数组就是拿数组去存放多个函数的地址

int Add(int x,int y)
{return x + y ;
}
int Sub(int x,int y)
{return x - y ;
}
int Mul(int x,int y)
{return x * y ;
}
int Div(int x,int y)
{return x / y ;
}
int main()
{//指针数组int* arr[5];//数组里有5个元素,每个元素的类型是int*类型的指针int (*pa)(int,int) = Add;//把Add函数名存进pa变量中,pa就是函数指针,(*pa)指向一个函数,参数是(int,int),返回类型也是int//现在我想把加减乘除四个函数都存在一个函数指针里,这时候就需要一个函数指针数组,这个数组可以存放4个函数的地址int (*parr[4])(int,int) = {Add,Sub,Mul,Div};//函数指针的数组 int i = 0 ;for(i=0;i<4;i++){printf("%d\n",parr[i](2,3));}return 0;
}
char* my_strcpy(char* dest, const char* src);
//写一个函数指针pf,能够指向my_strcpy
//类似int (*pa)(int,int) = Add;  类型 指针 参数 = 函数名
char*(*pf)(char*,const char*) = my_strcpy;
//写一个函数指针数组,能够存放4个my_strcpy函数的地址
char*(*pfArr[4])(char*,const char*) = {my_strcpy,my_strcpy,my_strcpy,my_strcpy};

看练习作业16,如何使用函数指针数组

7.指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针

int Add(int x,int y)
{return x + y;
}
int main()
{int arr[10] = {0};int(*p)[10] = &arr;//取出数组的地址,拿一个数组指针去存数组的地址
---------------------------------------------------类比上面 int(*pf)(int,int);//函数指针int(*pfArr[4])(int,int);//pfArr是一个函数指针的数组,数组存放4个元素,元素类型是int(*)(int,int),函数指针类型int(*(*ppfArr)[4])(int,int) = &pfArr;//取出函数指针的数组的地址,拿出一个数组指针去存函数指针的数组的地址//ppfArr 是一个数组指针,(*ppfArr)[4]指针指向的数组内有4个元素,每个元素的类型是int(*)(int,int),这个类型是函数指针类型//ppfArr 是一个指向[函数指针数组]的指针return 0;
}
int Add(int x,int y)
{return x + y;
}
int main()
{//指针数组int* arr[10];//数组指针int *(*pa)[10] = &arr;//函数指针int(*paAdd)(int,int)= Add;int sum = (*paAdd)(1,2);//*号在这里不起作用,因为paAdd本身就是函数地址,等价于Addprintf("sum = %d\n",sum);//函数指针存放函数地址,解引用得到函数,后面括号跟参数表示调用//函数指针的数组int(*pArr[5])(int,int);//指向函数指针数组的指针int(*(*ppArr)[5])(int,int) =  &pArr;//类型是函数指针类型return 0;
}

8.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进 行响应

void calc(int(*pf)(int,int));
void menu()
{printf("***********************************\n");printf("*****1.add           2.sub*********\n");printf("*****3.mul           4.div*********\n");printf("*****5.xor           0.exit********\n");printf("***********************************\n");
}
int Add(int x,int y)
{return x + y ;}
int Sub(int x,int y)
{return x - y ;}
int Mul(int x,int y)
{return x * y ;}
int Div(int x,int y)
{return x / y ;}
int Xor(int x,int y)//按位异或
{return x ^ y;}
int main()
{int input = 0;do{menu();printf("请选择:>");scanf("%d",&input);switch(input){case 0:printf("退出\n");case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 5:calc(Xor);break;default:printf("选择错误\n");}}while(input);
}
void calc(int(*pf)(int,int))//把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
{int x,y=0;scanf("%d%d",&x,&y);printf("%d\n",pf(x,y));
}
//void* 类型的指针,可以接收任意类型的地址
//void* 类型的指针,不能进行解引用操作
//void* 类型的指针,不能进行+-整数的操作

快速排序函数qsort

void qsort(void* base,size_t num,size_t width,int(*cmp)(const void* e1, const void* e2));
//第一个参数:待排序数组的首元素地址
//第二个参数:待排序数组的元素个数
//第三个参数:待排序数组的每个元素的大小,单位是字节
//第四个参数:是函数指针,比较两个元素的方法所用函数的地址,函数自己实现
//函数指针的两个参数是:待比较的两个元素的地址

9.指针和数组面试题的解析

类型转换

默认:整数就是int 小数就是double

控制流

------控制程序执行的方向

顺序结构

程序自上而下一步一步执行

分支结构

选择性地执行某一语句:if, switch ,goto

if(表达式)
{有且仅有一条一句;}
//{}为代码块,一个代码块可以把多个语句视作一条语句
//表达式成立,则执行该条语句,表达式不成立,则不执行该条语句;
if(表达式)//表达式成立,则执行该条语句,表达式不成立,则执行另一条语句;
{很多语句;}
else
{很多语句;}
if{}//1成立,执行A,if语句结束;1不成立,判断2,2成立,执行B,if语句结束,1,2不成立,判断3,执行C,if语句结束;若表达式都不成立,执行else,执行语句x,if结束
else if{}
else{}//中间可以写很多else if ,else语句可以省略
swith(表达式)//表达式成立 有一个值进入选项从而执行语句,多选一
{case 选项://选项只能是整数若干语句;break;case 选项:若干语句;break;default:若干语句;break;
}
goto 标签;//当程序运行到goto,就会跳转到标签所在的行继续往下执行,但标签不能冲突某语句;某语句;
标签:某语句

循环结构

for,while,do while

for(赋值表达式A; 判断表达式B;赋值表达式C)一套语句;//执行A,判断B是否成立,成立执行D,不成立退出循环,若执行完D,执行C,再重新进入循环
for(赋值表达式A; 判断表达式B;赋值表达式C){;}

优先级

字符串函数

操作对象是字符串,‘\0’

一、长度受限制的字符串函数

1.strlen函数

strlen函数返回的是在字符串中’\0’前面出现的字符的个数是无符号的整型

(1)strlen函数的使用

#include<stdio.h>
#include<string.h>
int main()
{char str1[] = "abcdef";printf("%d\n", strlen(str1));return 0;
}
if(strllen("abc")-strlen("abcdef")>0)printf("hehe");//无符号整型返回一定是正数

(2)模拟实现strlen函数

size_t strlen (const char* str);//原型
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t MyStrlen(const char* str1)//传一个地址,用char*接收,加个const, 指向的内容不能修改
{size_t len = 0;//size_t是无符号的整型assert(str != NULL)//指针要具备有效性,不能为空指针while (*str1 != 0)//遍历整个数组 //另一种写法while(*str) 不是0就能进去{++len;//计数++str1;//找下一个字符}return len;
}
int main()
{char* str1 = "abcdef";printf("%d\n", MyStrlen(str1));return 0;
}
---------------------------------------------------------------------------------要求:不创建临时变量求字符串长度  (递归)
int my_strlen(char* str)
{if (*str != '\0')//*str通过地址访问到b的字符return 1+my_strlen(str+1);//str指向b,str+1指向ielsereturn 0;
}
//递归的方法
//my_strlen("bit");
//1+my_strlen("it");
//1+1+my_strlen("t");
//1+1+1+my_strlen("");
//1+1+1+0+++++
//输出3
int main()
{char arr[] = "bit";//创建一个数组,里面是字符串 bitint len = my_strlen(arr);//arr是数组,传的是第一个元素的地址,数组的地址是连续的,传第一个后面加一就能一直找到。//我希望有个能够计算字符串长度的函数,需求是?printf("len = %d\n",len);return 0;
}

2.strcpy函数

char* strcpy(char* destination, const char * source);
//strcpy是覆盖拷贝,将source全覆盖拷贝到destination,会把’\0’也拷过去,且必须考虑destination的空间够不够
//(destination的空间必须>=source的空间)

(1)strcpy函数的使用

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{char p1[] = "abcdef";char* p2 = "hello";//'\0'会在f的位置strcpy(p1, p2);printf("%s\n", p1);printf("%s\n", p2);return 0;
}

(2)模拟实现strcpy函数

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
char* MyStrcpy(char * dest, const char * src)
{assert(dest != NULL);assert(src != NULL);while (*src)//也可以直接while(*dest++ = *src++) 就不需要最后给个结束标志{*dest = *src;//将src的字符拷贝到dest中去,没有将'0'拷贝进目标地++src;//下一个字符++dest;//下一个字符一一替换//可以直接*dest++ = *src++  ,先解引用再++}*dest = '\0';//源头最后字符等于'\0'结束
}
int main()
{char p1[] = "abcdef";const char* p2 = "hello";MyStrcpy(p1, p2);printf("%s\n", p1);printf("%s\n", p2);return 0;
}

3.strcat函数

char* strcat (char* destinaiton, const char * source);strcat追加拷贝,追加到目标空间后面,目标空间必须足够大,能容纳下源字符串的内容。、
源字符串必须以'\0'结束,目标空间必须可修改

(1)strcat函数的使用

#include<stdio.h>
#include<string.h>
int main()
{char p1[20] = "hello";const char* p2 = " world";strcat(p1, p2);//追加的时候w把o后的'\0'替换,追加后目的地里也要有'\0'printf("%s\n",p1);return 0;
}

(2)模拟实现strcat函数

#include<stdio.h>
#include<string.h>
void MyStrcat(char* dest, const char * src)//源头不用修改,目的必须可修改
{assert(dest != NULL);assert(src != NULL);//让dest指向'\0'位置//1.找到目的地'\0'的位置 while (*dest != '\0'){++dst;}//2.让dst从'\0'开始,将src赋值给dstwhile (*dest = *src)//等同while (*dest++ = *src++) {dest++;src++;}*dest = '\0';
}
int main()
{ char p1[20] = "hello";const char* p2 = " world";MyStrcat(p1, p2);printf("%s\n",p1);return 0;
}

4.strcmp函数

int strcmp(const char* str1, const char* str2);strcmp比较两个字符串的大小,一个字符一个字符比较,按ASCLL码比较标准规定:第一个字符串大于第二个字符串,则返回大于0的数字第一个字符串等于第二个字符串,则返回0第一个字符串小于第二个字符串,则返回小于0的数字

(1)strcmp函数的使用

#include<stdio.h>
#include<string.h>
int main()
{const char* p1 = "abcdef";const char* p2 = "abcdef";const char* p3 = "abcd";const char* p4 = "bcde";printf("%d\n", strcmp(p1,p2 ));printf("%d\n", strcmp(p1,p3 ));printf("%d\n", strcmp(p3,p4 ));
}

(2)模拟实现strcmp函数

#include<stdio.h>
#include<string.h>
int MyStrCmp(const char * str1, const char * str2)
{assert(str1 && str2);//保证不是空指针//逐个元素比较while (*str1 && *str2){if (*str1 > *str2){return 1;}else if (*str1 < *str2){return -1;}else //如果两个元素相等,进入下一个继续比较{++str1;++str2;}}//str2比较完了,str1还有if (*str1){return 1;}//str1比较完了,str2还有else if (*str2){return -1;}//str1等于str2else{return 0;}
}
-------------------------------------------------------------------------------另一种函数实现形式;
int MyStrCmp(const char * str1, const char * str2)
{assert(str1 && str2);//保证不是空指针while(*str1 == *str2)//字符串ascii相等 返回 0{if(*str1  == '\0'){return 0 ;}str1++;str2++;//一一对比}return(*str1 - *str2);
}
int main()
{char* p1 = "abcd";char* p2 = "abcd";char* p3 = "abcde";char* p4 = "bcd";char* p5 = "b";printf("%d\n", MyStrCmp(p1,p2 ));printf("%d\n", MyStrCmp(p1,p3 ));printf("%d\n", MyStrCmp(p1,p4 ));printf("%d\n", MyStrCmp(p4,p5 ));return 0;
}

二、长度受限制的字符串函数

6.strncpy函数

char * strncpy ( char * destination, const char * source, size_t num );num是字符
1.将源字符串len个字符复制到目标字符串。如果strlen(source)小于num,destination用额外的’\0’填 充到num长度。
2.如果strlen(source)大于等于num,那么只有num个字符复制到destination中

(1)strncpy函数的使用

int main()
{char arr1[10] =  "abcdef";char arr2[] = "hello bit";//只拷贝到四个字节,如果2小于1,则不够的全为0strncpy(arr1,arr2,4)return 0;
}

(2)模拟实现strncpy函数

void my_strncpy(char* destination, const char* source, size_t num)
{assert(destination && source);//destination 与 source不能是空指针char* des = destination;while (num) //需要复制字符个数{num--;if ((*destination++ = *source++) != '\0') //如果source解引用等于'\0',代表strlen(source)小于len,需要用NULL填充到len长度;elsebreak;}while (num) //用'\0'填充到len长度{num--;*destination++ = '\0';}return des;
}
---------------------------------------------------------------------------------源代码;
char* my_strncpy(char* destination, const char* source, size_t num)
{char* start = dest;while(num && (*dest++ == *source++))//复制 字符串num--;if(num)  //填充'\0'while(--num)*dest++ = '\0';return(start);
}int main()
{//char* arr1 = "abcdef;这种情况不行,因为要对arr1进行修改,不能是指针的形式,指针的形式为常量表达式,常量表达式不能被修改char arr1[10] =  "abcdef";char arr2[] = "hello bit";//只拷贝到四个字节,如果2小于1,则不够的全为0//strncpy(arr1,arr2,4)char* a =my_strncpy(arr1,arr2,4);_pritnf("%s\n",a);return 0;
}

7.strncat函数

char * strncat ( char * destination, const char * source, size_t num );
1.从字符串source的开头拷贝n个字符到destination字符串尾部,并追加’\0’字符。
2.destination要有足够的空间来容纳要拷贝的字符串。
3.如果n大于字符串source的长度,那么仅将src指向的字符串内容追加到dest的尾部。
4.字符追加完成后,再追加’\0’。

(1)strncat函数的使用

int main()
{char arr1[30] = "hello";char arr2[] = "world";//追加把o后的'0'替换掉,并追加'0'strncat(arr1,arr2,3);printf("%s\n",arr1);return 0;
}

(2)模拟实现strncat函数

char* my_strncat(char* destination, const char* source, size_t num)
{assert(destination && source);//destination 与 source不能是空指针char* des = destination;while(*destination != '\0') //找到目标字符串'\0'位置{destination++;}while (num) //需要追加字符个数{num--;if ((*destination = *source) != '\0'){destination++;source++;}else{break;}}*destination = '\0';return des;
}
-\-------------------------------------------------------------------------------n源代码;
char* my_strncat(char* destination, const char* source, size_t num)
{char* start = front;while(*front++);front--;while(num--)if(!(*front++ = *back++))return(start);*front = '\0';return(start);
}
int main()
{char arr1[30] = "hello";char arr2[] = "world";//追加把o后的'0'替换掉,并追加'0'strncat(arr1,arr2,3);printf("%s\n",arr1);return 0;
}

8.strncmp函数

int strncmp ( const char * str1, const char * str2, size_t num );
1.比较两个字符串,但最多比较len个字节。
2.如果两个字符串前len个字符相等返回0,str1大于str2返回大于0值,str1小于str2返回小于0的值

(1)strncmp函数的使用

int main()
{const char arr1[] = "abcdef";const char arr2[] = "hello";printf("%d\n",strncmp(arr1,arr2,4))return 0;
}

(2)模拟实现strncmp函数

int my_strncmp(const char* arr1,const char* arr2, int num)
{while(num){    while(*arr1 == *arr2){if(*arr1 == 0){return 0;}}arr1++;arr2++;num--;}return (*arr1 - *arr2);
}
int main()
{const char arr1[] = "abcdef";const char arr2[] = "bcdef";printf("%d\n",my_strncmp(arr1,arr2,4));return 0;
}

三、字符串查找函数

9.strstr函数

const char * strstr ( const char * str1, const char * str2 );
1.在str1查找整个str2第一次出现的位置,并返回一个指向该位置的指针。
2.如果str2没有完整地出现在str1的任何地方,函数返回NULL指针。
3.如果str2是一个空字符串,函数返回str1指针。返回值:str1中查找整个str2第一次出现位置的地址或NULLstr1:需要扫描的字符串str2:需要查找字符串(子串)

(1)strstr函数的使用

int main()
{char* arr1 = "abcdefghi";char* arr2 = "def";char* ret = strstr(arr1,arr2);if(ret == NULL){printf("子串不存在\n");}else{printf("%s\n",ret); //打印位置地址之后的字符串”defghi“ }return 0;
}

(2)模拟实现strstr函数

char* my_strstr(const char* arr1,const char* arr2)
{const char* s1 = arr1;const char* s2 = arr2;const char* cur = arr1;if(*arr2 == '\0')//arr是空的话直接返回arr1  还可以if(!*arr2){return (char*)arr1;}while(*cur)//从字符串首元素进入循环{s1 = cur;s2 = (char*)arr2;while((*s1 == *s2) && (*s2)&&(*s1 != '\0'))//若arr1的元素等于arr2的元素且arr1和arr2的元素还没指到尾0,*arr1指向0代表找不到,*arr2指向0代表已经找完了{s1++;//跳到下一个元素比s2++;}if(*s2 == '\0'){return (char*)cur;}cur++;//若arr1第一个元素不等于arr2第一个元素,arr1跳到下一个元素,再进入循环,让arr1第二个元素与arr2第一个元素比}return NULL;
}
int main()
{char* arr1 = "abcdefghi";char* arr2 = "def";char* ret = my_strstr(arr1,arr2);if(ret == NULL){printf("子串不存在\n");}else{printf("%s\n",ret); //打印位置地址之后的字符串”defghi“ }return 0;
}

10.strtok函数

char* strtok(char* str,const char* sep);
1.sep参数是个字符串,用来定义作分隔符的字符的集合
2.第一个参数是指定一个字符串,它包含了0个或者由多个sep字符串中一个或者多个分隔符分割的标记
3.strtok函数会找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)
4.strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将保存它在字符串中的位置
5.strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始查找下一个标记
如果字符串中不存在更多的编辑则返回NULL指针

(1)strtok函数的使用

int main()
{char arr[] = "940473066@qq.com";const char* p = "@.";char buf[1024] = {0};char* ret = NULL;strcpy(buf,arr);//拷贝一份字符串for(ret = strtok(buf,p); ret != NULL; ret = strtok(NULL,p))//for循环一直找切割标记并换成'\0',直到ret为空指针//strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将记住它在字符串中的位置//strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始查找下一个标记{printf("%s\n",ret);}return 0;}

(2)模拟实现strtok函数

11.strerror函数

char *strerror(int errnum);
功能:通过标准错误的标号,获得错误的描述字符串 ,将单纯的错误标号转为字符串描述。
参数:errnum:最新的错误标号。
返回值:指向错误信息的指针。

(1)strerror函数的使用

#include<errno.h>
int main()
{//错误码 - 对应的错误信息//0 -- NO error  //1 -- Operation not permitted//2 -- No such file or directory//errno -- 是一个全局的错误码变量,当c语言的库函数执行过程发生错误,赋值对应的错误码char* str  = strerror(errno);FILE* pf = fopen("test.txt","r");if(pf == NULL){printf("%s\n",strerror(errno));perror("malloc error")}else{printf("openm file success\n");}return 0;
}

(2)模拟实现strerror函数

内存函数

12.memcpy函数

void *memcpy(void *destination, void *source, size_t num);
以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。
函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度(字节)。
使用memcpy函数时,需要注意:
1.数据长度(第三个参数)的单位是字节(1byte = 8bit)。
2.注意该函数有一个返回值,类型是void*,是一个指向destin的指针。

(1)memcpy函数的使用

struct S
{char name[20];int age;
};
int main()
{int arr1[] = {1,2,3,4,5};int arr2[] = {0};struct S arr3[] = {{"张三",20},{"李四",30}};struct S arr4[3] = {0};memcpy(arr2,arr1,sizeof(arr1));memcpy(arr4,arr3,sizeof(arr3));return 0;
}

(2)模拟实现memcpy函数

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, int n)//因为memcpy是内存拷贝函数,所以必须什么类型都能接收,所以此处用void*做参数类型和返回值类型
{assert(dest != NULL);assert(src != NULL);char* ret = dest;//因为dest在循环体中不断发生变化,所以将dest的地址存放在指针变量ret中,此后ret就随着dest进行变化,但dest不会随着ret进行变化,最后函数返回ret的值while (n--){*(char*)dest = *(const char*)src;//void类型不能进行解引用操作,所以要进行强制类型转化,因为此处n指的是字节数,需要一个字节一个字节拷贝,所以强制类型转化为char*。++(char*)dest;//void*类型不能进行++操作,所以要进行强制类型转化,如果是后置++,那么强制类型转化先对dest进行,所以要进行前置++++(char*)src;}return ret;
}//arr1打印输出函数
void print(int arr1[], int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr1[i]);}printf("\n");
}int main()
{char str1[10] = { 0 };char* str2 = "abcdefg";char* ret = NULL;ret = my_memcpy(str1, str2, 5);//n代表的是字节数printf("%s\n", ret);int arr1[10] = { 0 };int arr2[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };my_memcpy(arr1, arr2, 40);//此处n是字节数,40个字节也就是10个int型值int sz = sizeof(arr1) / sizeof(arr1[0]);print(arr1, sz);system("pause");return 0;
}

13.memmove函数

void *memmove(void *dest, const void *src, size_t num);
1.memmove也是内存拷贝,可以解决内存重叠问题
2.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
3.如果源空间和目标空间出现重叠,就得使用memmove函数处理。

(1)memmove函数的使用

int main()
{int arr[] = {1,2,3,4,5,6,7,8,9,10};int i = 0;memmove(arr+2, arr, 20);for(i=0;i<10;i++){printf("%d",arr[i]);}return 0;
}

(2)模拟实现memmove函数

#include<stdio.h>
#include<string.h>
void* my_memmove(void* dst, const void* src, size_t num)
{void* ret = dest;//从后往前拷if (dst >= src && dst <= (char*)src + num)//分析在这个区间内dest>src&& dest<src+num{while(num--){*((char*)dest + num) = ((char*)src + num);//目标首地址加字节数等于dest的最后端,再找到src的最后端,解引用赋值上去;}}//从前往后拷else(dst<src){while(num--){*(char*)dest = *(char*)src;++*(char*)dest;++*(char*)src;}}return ret;
}
int main()
{int a[10] = { 1, 2, 3, 4, 5 };my_memmove(a + 4, a, 20);return 0;
}

13.memcmp函数

int memcmp(const void \*str1, const void \*str2, size_t n);
//字符串大小的比较是以ASCII 码表上的顺序来决定,次顺序亦为字符的值。memcmp()首先将s1 第一个字符值减去s2 第一个字符的值,若差为0 则再继续比较下个字符,若差值不为0 则将差值返回。

(1)memcmp函数的使用

int main()
{int arr1[] = {1,2,3,4,5};int arr2[] = {1,2,5,4,3};int ret = memcmp(arr1,arr2,9);printf("%d\n",ret);return 0;
}

(2)模拟实现memcmp函数

#include<stdio.h>
#include<assert.h>
int my_memcmp(const void* buf1, const void* buf2, size_t count)
{assert(buf1 != NULL && buf2 != NULL);const char* pf1 = (const char*)buf1;const char* pf2 = (const char*)buf2;int res = 0;while (count-- != 0){res = *pf1 - *pf2;if (res != 0)break;pf1++;pf2++;}return res;
}
void main()
{char str1[20] = "hello";char str2[20] = "helloworld";int a = my_memcmp(str1, str2, 3);printf("%d", a);
}

14.bzero和menset函数

bzero() 能够将内存块(字符串)的前n个字节清零,在"string.h"头文件中,原型为:void bzero(void *s, size_t n);【参数】s为内存(字符串)指针,n 为需要清零的字节数。bzero()将参数s 所指的内存区域前n 个字节全部设为零。void* 空类型,万能指针,但无法使用,若要使用,必须强转menset()把s所指内存区域的前count个字节设置成c的值。原型:
void *memset(void *s,int c,size_t n)s指向要填充的内存块。c是要被设置的值。n是要被设置该值的字符数。字节返回类型是一个指向存储区s的指针。
--------------------------------------------------------------------------------
int main()
{char arr[10] = "";memset(arr,'#',10);return 0;
}

字符分类函数/字符转换函数····

内存映射

代码段(文本段)

代码段是用来存放可执行文件的操作指令,可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,它是不可写的。

代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。

这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。

在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

常量区rodata

存放一般的常量、字符串常量等。这块内存只有读取权限,没有写入权限,因此他们的值在程序运行期间不能改变。const 修饰的不可改变 ,不一定在常量区 。 volition 告诉编译器变量是**易变的,**无非就是希望编译器去注意该变量的状态,时刻注意该变量是易变的,每次读取该变量的值都重新从内存中读取。

data段

已初始化且初值不为0的全局变量和静态局部变量

数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。

数据段属于静态内存分配。

BSS段

bss段属于静态内存分配。 bss是英文Block Started by Symbol的简称。

BSS段通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0,所以,未初始的全局变量在程序执行之前已经成0了。

特点是:可读写的

*BSS段在执行文件中时候不占磁盘空间,要运行的时候才分配空间并清0.当可执行文件加载运行前,会为BSS段中的变量分配足够的空间并全部自动清理(因此,才有未初始化的全局变量的值为0的说法)。BSS段主要是为了节省可执行文件在*磁盘****上所占的空间,其仅仅记录变量所需的大小。对未初始化的大型数组的节省效率比较明显。

heap

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

stack

栈是用户存放程序临时创建的局部变量,也就是函数中定义的变量(但不包括 static 声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

信息不丢失的方法:1.static 2.放到常量区 3放在全局变量和堆上

#include <stdio.h>
#include <stdlib.h>
int g_var1; // g_var1是未初始化的全局变量,存放在数据段的BSS区,其值默认为0;
int g_var2=20; // g_var1是初始化了的全局变量,存放在数据段的DATA区,其值为初始化值20;
int main(int argc, char **argv) //argv里存放的是命令行参数,他存放在命令行参数区
{static int s_var1; // s_var1是未初始化的静态变量,存放在数据段的BSS区,其值默认为0;static int s_var2=10; // g_var1是初始化了的静态变量,存放在数据段的DATA区,其值为初始化值10;char *str="Hello"; // str是初始化了的局部变量,存放在栈(STACK)中,其值是"Hello"这个字符串常量存放
在DATA段里RODATA区中的地址char *ptr; // ptr是未初始化了的局部变量,存放在栈(STACK)中;其值为随机值,这时候的ptr称 为“野指针(为初始化的指针)”ptr = malloc(100); // malloc()会从堆(HEAP)中分配100个字节的内存空间,并将该内存空间的首地址返回给ptr存 放;printf("[cmd args]: argv address: %p\n", argv);printf("\n");printf("[ Stack]: str address: %p\n", &str);printf("[ Stack]: ptr address: %p\n", &ptr);printf("\n");printf("[ Heap ]: malloc address: %p\n", ptr);printf("\n");printf("[ bss ]: s_var1 address: %p value: %d\n", &s_var1, g_var1);printf("[ bss ]: g_var1 address: %p value: %d\n", &g_var1, g_var1);printf("[ data ]: g_var2 address: %p value: %d\n", &g_var2, g_var2);printf("[ data ]: s_var2 address: %p value: %d\n", &s_var2, s_var2);printf("[rodata]: \"%s\" address: %p \n", str, str);printf("\n");printf("[ text ]: main() address: %p\n", main);printf("\n");return 0;
}

结构体内存对齐(结构体的存储方式)

考点 如何计算?

首先得掌握结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8 ,gcc无默认对齐数
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
#include<stdio.h>
struct S1
{char c1;//第一个成员在与结构体变量偏移量为0的地址处。  int a;//int大小是4,对齐数是8,较小值是4,因此是4char c2;//char大小是11,对齐数是8,较小值是1,因此是1//结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。//1+4+1=9 但必须是4的倍数,要再浪费3个空间,12字节
};
struct S2
{char c1;//1char c2;//1int a;//4,但总大小要是4的倍数,为8
};
int main()
{struct S1 s1 = {0};struct S2 s2 = {0};printf("%d\n",sizeof(s1));printf("%d\n",sizeof(s2));return 0;
}

手动修改结构体的对齐方式

#pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。
#pragma pack(8)//设置默认对齐数为8
41
#i0096+32.+*-*/*/nclude<steddef.h>
offsetof(structNanme,memberName); 宏 计算成员对于结构体的偏移量
改变对齐方式,但指针依然只有操作系统有有关,64bit-8 32bit-4

位域

位域:
1》概念:带有预定宽度的变量称为位域
2》姿势:struct 标签{位域类型 位域变量名:位域宽度;位域类型 位域变量名:位域宽度;};例子:struct xx{int a:2;           //占2位     00  01  10   11unsigned b:4;      //占4个位};unsigned age:8;    200   256      11111111     5003》位域类型:位域类型为整型例: char  short  int   unsigned int(简写为unsigned)4》位域宽度:不能超过该类型的的大小例子:int a:33    //错误赋值时不能超过该类型的承受范围例子:unsigned char a:2  // 那么a只能存 0  1  2   3  这几个数字a = 5  ;           //错误4》位域对齐:位域类似结构体,按照最大类型对齐5》空域:位域宽度为0的位域称为空域,其作用是:该次对齐空间剩余部分全部补0例子:struct xx{int b:1 ;int :0 ;   // 32位对齐,b用了1位,所以该处共有31个0int c:3  ;   // 新的对齐行}6》无名位域:位域没有名字,仅仅占用空间,无名位域不能调用,其作用是隔开两个位域例子:struct xx{unsigned a:2unsigned: 3        //无名位域,将a和c隔开3个位,该3个位不能使用unsigned c:2 }7》位域大小计算(存储规则):1>如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止2>如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍3>如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++,GCC采取压缩方式4>如果位域字段之间穿插着非位域字段,则不进行压缩

结构体传参

struct S
{ int data[1000]; int num;
};
struct S s = {{1,2,3,4}, 1000};
void print1(struct S s) //结构体传值
{printf("%d\n", s.num);
}
void print2(struct S* ps) //结构体传地址
{ printf("%d\n", ps->num);
}
int main(){ print1(s);  //传结构体  print2(&s); //传地址 return 0;
}

位段

位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是 int、unsigned int 或 signed int 。 最大32bit

2.位段的成员名后边有一个冒号和一个数字。

struct S
{//位段类似结构体,成员不一样//位段 -位-二进制位int _a :2;//a只需要2个bit位int _b :5;//b只需要5个bit位int _c :10;int _d :30;};
int main()
{struct S s;printf("%d\n",sizeof(s));//8return 0;
}

位段内存分配

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节32bit( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

枚举

枚举类型的定义

enum Sex
{MALE,//0FEMALE,//1SECRET//2
};
int main()
{enum Sex s = MALE;///只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。return 0;
}

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值

枚举的优点

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

联合(共用体)

联合的定义

联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。

union Un//联合类型声明
{char c;//1int i;//4
};
int main()
{union Un u;//联合变量的定义printf("%d\n",sizeof(u));//计算联合变量的大小 4字节return 0;
}

联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

int check_sys()//判断大小端存储
{union Un{char c;int i;}u;u.i = 1;//返回1小端 返回0大端return u.c;
}
int main()
{int a = 1;int ret = check_sys();if(1 == ret){printf("小端\n");}elseprintf("大端\n");return 0;
}

联合大小的计算

联合的大小至少是最大成员的大小。 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

*最大的那个变量占用的字节数为联合体的总字节数
*该总字节数必须满足其他成员的整数倍,如果不满足,则自动往后延伸

union Un
{int a;//4char arr[5];//5
};
int main()
{union Un u;printf("%d\n",sizeopf(u));//8return 0;
}

通讯录实现

共用体 { 结构体 }

a. 联合体的大小是大于等于 [所有成员中 [最小的 [最大非结构体成员大小的整数倍]]]。
(非常拗口,可以分解为 联合体的大小满足: 1.大于等于所有成员;2. 是所有非结构体成员的整数倍)
b. 联合体的首地址是和Size有关的,规则同联合体本身的规则。

结构体 { 结构体 }

a. 结构体中嵌套结构体,其结构体的Size是相当于将内层结构体展开后,重新组成的整个结构体进行Size的计算。(这里有明显的Recursive适用性)
b. 结构体嵌套结构体,其起始地址与Size相关,其规则与结构体本身规则相同。

结构体{ 共用体 }

a. 结构体中含有共用体时,结构体的大小 等于将共用体展开后,所有的成员中最大宽度的成员宽度作为对齐规则。
b. 地址规则只与 Size 有关,遵循结构体的规则。

共用体 { 共用体 }

a. 共用体中嵌套共用体,其大小的对齐规则以将内部共用体展开后的最大类型的大小为标准,再进行对齐。
b. 共用体中嵌套共用体,其起始地址只与 Size 有关,规律同共用体。

条件编译:

// 平台兼容

* 有选择地编译代码if  else if  else ----》 在运行的时候 进行分支选择#if  #elif  #else  #endif --->  在编译的时候,进行编译选择

* #if  #elif  #else  #endif例子:#if 1#elif 1#else#endif* #ifdef    #endif
* #ifndef   #endif
基本姿势:#if  判断若干条语句;#endif 
----> 判断是否为真,如果为真 则将 若干条语句 编译到代码中#if  判断若干条语句A;
#else若干条语句B;
#endif #if  判断A若干条语句A;
#elif 判断B若干条语句B;
#elif 判断C若干条语句C;
#else若干条语句D;
#endif #ifndef  宏若干条语句A;
#endif
-----》 如果没有定义 宏  那么成立  编译  若干条语句A;
-----》 如果宏存在,则不编译  若干条语句A
#ifdef  宏 若干条语句A;
#endif
-----》 如果定义 宏  那么成立  编译  若干条语句A;
-----》 如果该宏不存在,则不编译  若干条语句A

可变参数

minprintf(char *fmat,...){va_list ap;                        //定义指针va_start(ap,fmat);                //初始化 将ap指向fmat//分析 fmat  读取每一个字符 判断是否 为 %int a = va_arg(ap,int);          //提取intshort c = va_arg(ap,short);     //提取shortva_end(ap);        //结束}printf("%d %hd ",a,b);
#ifdef _WIN32//操作系统判断
int isL=0x37;
#else
int isL=0x57;
#endifint myPrintf(const char *frm, ...) {int cnt = 0, y, z;va_list arg;va_start(arg, frm);//开始遍历for (int i = 0;frm[i]; ++i)switch (frm[i]) {case '%': switch (frm[++i]) {case '%': putchar(frm[i]);++cnt;break;

scanf

scanf输入字符串(含有空格的字符串,例如:“I love you!”)时,总是在空格处停止扫描。我们用scanf(“%s”,str);输入 “I love you!” 字符串后,str输出却只有 “I” ,这并不是我们想要的。这是因为scanf扫描到 “I” 后面的空格,就认为对str的扫描结束(即空格没有被扫描),并舍弃后面的" love you!",只得到了 “I” 。

解决办法

在这里给出了两种解决办法,可以让空格也被扫描到str里。

1. **gets()函数** ,用gets()替代scanf();gets可以无限读取字符串,不会判断上限,以回车结束读取。其用法为gets(s),其中s为字符串变量(字符串数组名或字符串指针)。简单的理解就是读入一串字符(遇到回车结束),存到
2. scanf("%[^\n]",str)** ,遇到"\n"结束'^'含有非的意思“%[^\n]“即遇到\n结束。如果使用”%[^v]”,那我们输入 “I love you!” ,输出的就是 “I lo” ,现在能懂这个非的意思了吧…

http://c.biancheng.net/cpp/html/34.html (关于scanf的种种)

动态内存管理

1.动态内存函数(在堆上操作)

1、malloc和free

void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。(该空间的第一个地址)
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
void free (void* ptr);
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
int main()
{//向内存申请10个整型空间int* p = (int*)malloc(10*sizeof(int));// 申请的空间一定要是类型的整数倍if(p == NULL){//打印错误原因的一种方式printf("%s\n",strerror(errno));}else{//正常使用空间int i =0;for(i=0;i<10;i++){*(p+i) = i;//下标为i的元素,等于p[i] = i}for(i=0;i<10;i++){printf("%d ",*(p+i));    }//当动态申请的空间不再使用时候,就应该还给系统free(p);//虽然申请的空间被释放了,但p指针仍然能指向这块空间,非法访问p = NULL;//需要给p一个空指针}return 0;
}

2、calloc

void* calloc (size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
int main()
{int* p = (int*)calloc(10,sizeof(int));//堆上开辟空间,初始化为0if( p == NULL){//打印错误原因的一种方式printf("%s\n",strerror(errno));}else{int i =0;for(i=0;i<10;i++){printf("%d ",*(p+i));   }}//释放空间free(p);//free释放动态开辟的空间p = NULL;return 0;
}

3、realloc

realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。

函数原型如下:

void* realloc (void*  ptr, size_t size);
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
int main()
{int*p = (int*)malloc(20);if( p == NULL){//打印错误原因的一种方式printf("%s\n",strerror(errno));}else{int i =0;for(i=0;i<5;i++){*(p+i) = i;//使用开辟的20个字节的空间,假设20个字节不满足使用,需要40}int* p2 = (int*)realloc(p,40);//调整动态空间大小}//释放空间free(p);//free释放动态开辟的空间p = NULL;//realloc使用注意事项://1.如果p指向空间之后有足够内存空间追加,则直接追加后返回p//2.如果p指向空间之后无足够空间,则realloc重新找一块内存空间开票,拷贝数据,自动释放旧空间,最后返回p1//3.需要追加空间太大,新旧空间都会丢,得用新变量来接收realloc函数返回值return 0;
}
------------------------------------------------------------------------int *p = realloc(NULL,40);// realloc实现malloc功能return 0;

2.常见的动态内存错误

1、对NULL指针的解引用操作

int main()
{int* p = (int*)malloc(40);//万一malloc失败了,p被赋值为NULL*p = 0;//err,一定要判断是否是空指针int i = 0;for(i=0;i<10;i++){*(p+i) = i;//对空指针解引用有问题}free(p);p = NULL;return 0;
}

2、对动态开辟空间的越界访问

int main()
{int* p = (int*)malloc(5*sizeof(int));if(p == NULL){return 0;}else{int i = 0;for(i=0;i<10;i++){*(p+i) = i;//空间只有5个整型空间,for循环访问10个,越界访问}}free(p);p =  NULL;return 0;
}

3、对非动态开辟内存使用free释放

void test()
{ int a = 10;int *p = &a; free(p);//非法
}

4、使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

5、对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);//p不再指向动态内存的起始位置 ,谁申请谁回收//p= NULL; 防止自己释放两次free(p);//重复释放
}

6、动态开辟内存忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if(NULL!= p){*p = 20;//忘记释放不再使用的动态开辟的空间会造成内存泄漏。}
}
int main()
{test();while(1);
}

柔性数组

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大 小,以适应柔性数组的预期大小。

struct S
{int n;int arr[0];//柔性数组成员 -数组大小是可调整的
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S)+5*sizeof(int));ps->n = 100;int i = 0;for(i=0;i<5;i++){ps->arr[i] = i;//0 1 2 3 4}return 0;
}

连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。(该空间的第一个地址)
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。


```c
void free (void* ptr);
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
int main()
{//向内存申请10个整型空间int* p = (int*)malloc(10*sizeof(int));// 申请的空间一定要是类型的整数倍if(p == NULL){//打印错误原因的一种方式printf("%s\n",strerror(errno));}else{//正常使用空间int i =0;for(i=0;i<10;i++){*(p+i) = i;//下标为i的元素,等于p[i] = i}for(i=0;i<10;i++){printf("%d ",*(p+i));    }//当动态申请的空间不再使用时候,就应该还给系统free(p);//虽然申请的空间被释放了,但p指针仍然能指向这块空间,非法访问p = NULL;//需要给p一个空指针}return 0;
}

2、calloc

void* calloc (size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
int main()
{int* p = (int*)calloc(10,sizeof(int));//堆上开辟空间,初始化为0if( p == NULL){//打印错误原因的一种方式printf("%s\n",strerror(errno));}else{int i =0;for(i=0;i<10;i++){printf("%d ",*(p+i));   }}//释放空间free(p);//free释放动态开辟的空间p = NULL;return 0;
}

3、realloc

realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时 候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小 的调整。

函数原型如下:

void* realloc (void*  ptr, size_t size);
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc在调整内存空间的是存在两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
int main()
{int*p = (int*)malloc(20);if( p == NULL){//打印错误原因的一种方式printf("%s\n",strerror(errno));}else{int i =0;for(i=0;i<5;i++){*(p+i) = i;//使用开辟的20个字节的空间,假设20个字节不满足使用,需要40}int* p2 = (int*)realloc(p,40);//调整动态空间大小}//释放空间free(p);//free释放动态开辟的空间p = NULL;//realloc使用注意事项://1.如果p指向空间之后有足够内存空间追加,则直接追加后返回p//2.如果p指向空间之后无足够空间,则realloc重新找一块内存空间开票,拷贝数据,自动释放旧空间,最后返回p1//3.需要追加空间太大,新旧空间都会丢,得用新变量来接收realloc函数返回值return 0;
}
------------------------------------------------------------------------int *p = realloc(NULL,40);// realloc实现malloc功能return 0;

2.常见的动态内存错误

1、对NULL指针的解引用操作

int main()
{int* p = (int*)malloc(40);//万一malloc失败了,p被赋值为NULL*p = 0;//err,一定要判断是否是空指针int i = 0;for(i=0;i<10;i++){*(p+i) = i;//对空指针解引用有问题}free(p);p = NULL;return 0;
}

2、对动态开辟空间的越界访问

int main()
{int* p = (int*)malloc(5*sizeof(int));if(p == NULL){return 0;}else{int i = 0;for(i=0;i<10;i++){*(p+i) = i;//空间只有5个整型空间,for循环访问10个,越界访问}}free(p);p =  NULL;return 0;
}

3、对非动态开辟内存使用free释放

void test()
{ int a = 10;int *p = &a; free(p);//非法
}

4、使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

5、对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);//p不再指向动态内存的起始位置 ,谁申请谁回收//p= NULL; 防止自己释放两次free(p);//重复释放
}

6、动态开辟内存忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if(NULL!= p){*p = 20;//忘记释放不再使用的动态开辟的空间会造成内存泄漏。}
}
int main()
{test();while(1);
}

C语言笔记(小白发给自己看的)相关推荐

  1. c语言程序和plc程序的区别,一文告诉你PLC与计算机的本质区别在哪里!小白都能看懂!...

    原标题:一文告诉你PLC与计算机的本质区别在哪里!小白都能看懂! 你真的了解PLC吗?你知道PLC与计算机的本质区别吗?我来简单解释一下吧. 1.PLC可以工作在极其恶劣的电磁环境中 如果我们把计算机 ...

  2. PHP笔记——java程序员看懂PHP程序

    PHP笔记--java程序员看懂PHP程序 php是一种服务器端脚本语言,类型松散的语言. <?php   ?>       xml风格 <script language=" ...

  3. STM32中C语言知识点:初学者必看,老鸟复习(长文总结)

    说在前面的话 一位初学单片机的小伙伴让我推荐C语言书籍,因为C语言基础比较差,想把C语言重新学一遍,再去学单片机,我以前刚学单片机的时候也有这样子的想法. 其实C语言是可以边学单片机边学的,学单片机的 ...

  4. 合并相同数据的行_R语言笔记(六):数据框重塑(reshape2)

    数据处理主要内容包括: 1. 特殊值处理 1.1 缺失值 1.2 离群值 1.3 日期 2. 数据转换(base vs. dplyr) 2.1 筛选(subset vs. filter/select/ ...

  5. 五分钟教你使用vue-cli3创建项目(三种创建方式,小白入门必看)

    五分钟教你使用vue-cli3创建项目(三种创建方式,小白入门必看) 一.搭建vue环境 安装Nodejs 官网下载Nodejs,如果希望稳定的开发环境则下LTS(Long Time Support) ...

  6. 【C语言程序设计进阶-浙大翁恺】C语言笔记 文件

    [C语言程序设计进阶-浙大翁恺]C语言笔记 文件 文件 格式化输入输出 文件输入输出 二进制文件 位运算 按位运算 移位运算 位运算例子 位段 文件 格式化输入输出 %-nd:数字左对齐,且输出要占n ...

  7. [免费专栏] Android安全之Android Xposed插件开发,小白都能看得懂的教程

    也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大 少走了弯路,也就错过了风景,无论如何,感谢经历 Android安全付费专栏长期更新,本篇最新内容请前往: [ ...

  8. R语言笔记1:t检验和Wilcoxon检验

    转自新浪博客,转载地址:http://blog.sina.com.cn/s/blog_427c24ae0102wg7n.html 1.t检验 数据是高血压患者治疗前后舒张压的变化,这个内容最熟悉不过了 ...

  9. R语言笔记——”org.Hs.eg.db“脱坑记录

    R语言笔记--"org.Hs.eg.db"脱坑记录 "org.Hs.eg.db"是发布在bioconductor平台上面的一个数据库文件,该包中装有较多的主流数 ...

最新文章

  1. Windows下超详细安装Anaconda3以及jupyter notebook
  2. JavaWeb编程(十)Json语句
  3. Promise详解(一) ----基础用法
  4. DbVisualizer Personal 7.0 数据库连接工具免安装版本获取,直接解压即可使用!
  5. SSM实现导出报表为Excel
  6. 2017年10月05日普及组 负进制
  7. 个人贷款5级分类,来看看你是哪一级
  8. 45岁,一个平凡大叔的异地打工生活
  9. 项目进度管理和项目成本管理的重点梳理
  10. [人工智能教程] 人工智能暑期课实践项目建议
  11. Mybatis Generator 自动生成数据库XML(Mybatis Generator 逆向工程)
  12. vue 点击文字input_vue input实现点击按钮文字增删功能示例
  13. 每天学一点Flash(55) 认识类的结构
  14. 文件的查找与压缩归档
  15. 计算题合集-管理经济学
  16. fsck fat32修复工具
  17. iOS 关于键盘监听
  18. linux个人学习记录
  19. Sms开源短信及消息转发器,不仅只转发短信,备用机必备神器
  20. 在 Linux 上以 All-in-One 模式安装 KubeSphere

热门文章

  1. electron-forge 脚手架安装错误及解决方法
  2. java程序设计教学计划_《Java语言程序设计》课程设计大纲
  3. 用4+1架构视图说说Flink架构
  4. c语言监控程序,C语言写监控守护进程
  5. 四篇关于chen_zhe的美文
  6. 数通-数据通信网络基础
  7. Hyper-V内的虚拟机复制粘贴
  8. IAR工程编译报错Warning[Pe223]: function “Get_Tempreture“ declared implicitly......
  9. 好用的企业网盘在这里哦!
  10. mybatis传单个参数报错:There is no getter for property named 'user_id' in 'class java.lang.String