1、客户端两种主流的接口模型:
#ifndef _SOCKETCLINET_H #endif _SOCKETCLINET_H #ifdef __cplusplus //如果用了C++的编译器,用C语言的规范来引用 extern "C" { #endif //socket客户端环境初始化 int socketclient_init(void** handle); //socket客户端报文发送 int socketclient_send(void* handle, unsigned char* buf, int buflen); //socket客户端报文接收 int socketclient_recv(void* handle, unsigned char* buf, int buflen); //socket客户端环境释放 int socketclient_destroy(void *hanlde); //第二套API函数 //socket客户端环境初始化 int socketclient_init2(void** handle); //socket客户端报文发送 int socketclient_send2(void* handle, unsigned char* buf, int buflen); //socket客户端报文接收 int socketclient_recv2(void* handle, unsigned char** buf, int* buflen); //socket客户端环境释放 int socketclient_destroy2(void** hanlde); #ifdef __cplusplus } #endif #endif
2、数组做函数参数的退化问题
数组做函数参数时会退化为一个指针
通常形参是数组类型时,会同时附带上数组的长度的参数
//void printArray(int a[6],int len) //void printArray(int a[],int len){ void printArray(int *a,int len){ int i; int len2=0; le2=sizeof(a)/sizeof(a[0]);//1 //在这里,实参的a和形参的a数据类型是不一样的,这里的实参是数组类型,而形参是指针类型 //因此实参中数组的长度是sizeof(a[0])*sizeof(a),而形参中指针的长度是4 //传进来的形参的形式不管是int a[] 还是int a[6] 还是int *a C编译器都会把它当作指针类型 //编译器之所以这样做是因为如果形参当中的a如果也是数组类型的话,就相当于将实参中的数组中所有的数据都拷贝一份到形参中,无形之中降低了C语言编译期的效率 //而通过指针类型的形参操作数据是C语言的特点 //形参写在函数里面和写在函数定义的括号里面是一样的,写在函数定义的括号里面拥有对外的属性 for(i=0;i<len;i++){ printf("%d",a[i]); } } void main(){ int a[]={1,2,3,4,5,6}; int len=sizeof(a)/sizeof(a[0]); printArray(a,len); }
3、数据类型
数据类型是为了方便的表示现实中的事物
类型相同的数据有相同的表示形式、存储格式以及相关的操作
数据类型可以理解为创建对象的模具,是固定内存大小的别名
int a;//告诉C编译期分配4个字节的内存 int b[10];//告诉C编译期分配40个字节的内存 printf("%d",b);//1244972 printf("%d",b+1);//1244976 printf("%d",&b);//1244972 printf("%d",&b+1);//1245012 //b+1 &b+1 结果不一样 b &b所代表的数据类型不一样 //b是数组首元素的地址 //&b是整个数组的地址 //b &b 数组数据类型 //区分数组类型 数组指针 数组类型和数组指针类型的关系 下节课才讲 printf("%d",sizeof(b));//40 printf("%d",sizeof(a));//4
4、用struct可以给数据类型起别名,通过struct Teacher定义的类型在定义变量时必须通过struct Teacher定义,即struct不可以省略
struct Teacher{ char name[64]; int age; }Teacher; void main(){ int a; int b[10]; struct Teacher t1; //通过struct Teacher定义的类型在定义变量时必须通过struct Teacher定义,即struct不可以省略 t1.age=31; printf("hello...\n"); }
通过typedef struct Teacher定义的类型在定义变量时可以省略掉struct
typedef struct Teacher{ char name[64]; int age; }Teacher; void main(){ int a; int b[10]; Teacher t1; //通过typedef struct Teacher定义的类型在定义变量时可以省略掉struct t1.age=31; printf("hello...\n"); }
5、typedef还可以对基本类型重命名
typedef int u32;
6、void*
void 字面意思是无类型
void* 无类型指针
void* 可以指向任何类型的数据
用法1:数据类型的封装
int initHardEnv(void** handle); void* memcpy(void* dest,const void* src,size_t len); void* memset(void* buffer,int c,size_t num);
用法2:void修饰函数返回值和参数,仅表示无
如果函数没有返回值,那么应该将其声明为void型
如果函数没有参数,应该将其声明为void
int function(void){ return 1; }
void指针的意义
C语言规定只有相同类型的指针才可以相互赋值
void*指针作为左值用于接收任意类型的指针
void*指针作为右值赋值给其他指针时需要强制类型转换
int* p1=NULL;
char* p2=(char*)malloc(sizeof(char)*20); //malloc返回的是一个void*类型的指针
7、变量
变量概念:既可以读又可以写的内存对象称为变量,若一旦初始化后不能修改的对象则称常量
变量本质:程序通过变量来申请和命名内存空间int a=0;
通过变量名可以访问一个或一段内存空间
8、修改变量有几种方法?
直接改
间接改 内存有地址编号,拿到变量对应的地址编号也可以修改变量的值
内存空间可以再取给别名吗?
9、数据类型和变量的关系是通过数据类型定义变量
10、直接操作内存地址
int a; int b; char* p; a=10; printf("%d",&a); //1245024 //直接操纵内存地址 *((int*)1245024)=200; //让编译器以int*的方式处理1245024这块内存,外面的*意思是拿到这块内存,然后再将200放到这块内存中 ========================== char *p; { p=1245024;//将门牌号赋给指针变量p *p=300;//*p代表拿到1245045这块内存空间,然后将300放入这块内存空间中 }
11、内存四区
操作系统把C代码分成栈 堆 代码区 全局区
栈:由编译器自动分配释放,存放函数的参数值,局部变量的值
堆:由程序员分配释放
全局区(静态区):全局变量和静态变量的存储是放在一块儿的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放
常量区:字符串常量和其他常量的存储位置,程序结束后由操作系统释放
程序代码区:存放函数体的二进制代码
12、静态存储区
char* getStr1(){ char* p="abcdefg1"; return p1; } char* getStr2(){ char* p="abcdefg2"; } void main(){ char* p1=NULL; char* p2=NULL; p1=getStr1(); p2=getStr2(); //打印p1 p2所指向内存空间的数据 printf("%s",p1);//abcdefg1 printf("%s",p2);//abcdefg2 printf("%d",p1);//4282320 printf("%d",p2);//4282456 //p1和p2的地址不相同 两个函数返回值不一样时,词法分析后的结果有两个 }
char* getStr1(){ char* p="abcdefg2"; return p1; } char* getStr2(){ char* p="abcdefg2"; } void main(){ char* p1=NULL; char* p2=NULL; p1=getStr1(); p2=getStr2(); //打印p1 p2所指向内存空间的数据 printf("%s",p1);//abcdefg1 printf("%s",p2);//abcdefg2 printf("%d",p1);//4282320 printf("%d",p2);//4282320 //p1和p2的地址不同 两个函数返回值一样时,这是因为词法分析后的结果只有一个 }
13、堆区
char *getMem(int num){ char *p1=NULL; p1=(char*)malloc(sizeof(char)*num); if(p1==NULL){ return NULL; } return p1; } void main(){ char* tmp=NULL; tmp=getMem(10); if(tmp==NULL){ return; } strcpy(tmp,"111222");//往tmp指向的内存空间拷数据 printf("%s",tmp);//111222 }
14、栈区
char *getMem2(){ char buf[64];//临时变量,栈区存放 strcpy(buf,"123456789"); printf("%s",buf);//12345678 return buf;//这里的return是把内存块的首地址返回给tmp了 } void main(){ char* tmp=NULL; tmp=getMem2();//返回的buf已经被释放了,没法引用,这时程序没有当机已经很不错了 printf("%s",tmp);//什么都没有 }
15、如果在main函数中调用了函数fa,fa中开辟的栈内存在main中不可以访问,fa中开辟的堆内存在main中可以访问
16、局部函数中在全局区定义的常量,如"abcdefg"也可以在main函数里面调用
17、指针变量和它所指向的内存块是两个不同的概念
含义1:给p赋值p=0x1111,只会改变指针变量值,不会改变所指的内容
含义2:给*p赋值*p=‘a‘,不会改变指针变量的值,只会改变所指的内存块的值
含义3:左边*p表示给内存赋值 右边*p表示取值
含义4:左边 char* p1是定义指针
含义5:想要通过指针修改内存中的值时一定要保证所指的内存块能修改
18、常量区不可以修改
char* getStr(){ char* tmp=NULL; tmp="abcdefg"; return tmp; } void main(){ char *p=getStr(); *(p+2)=‘r‘;//常量区是不可以修改的,直接会报错 }
19、指针也是一种数据类型,这个数据类型是指它指向的内存空间的数据类型,指针步长根据所指内存空间类型来定
20、指针做函数参数
指针做函数参数 形参有多级指针的时候
站在编译器角度,只需要分配4个字节的内存
当我们使用内存的时候,我们才关心指针所指向的内存,是一维的还是二维的
21、野指针案例
char *p1=(char *)malloc(100); strcpy(p1,"111222"); if(p1!=NULL){ free(p1); p1=NULL;//这一步一定要加,否则p1就成了野指针,因为我们free掉p1的时候是将其对空间的内存释放了,但是p1这个变量里面还存着某个地址,所以需要手动清空它 //如果不释放的话,当后面的代码再用if判断p1!=NULL从而去判断是否free(p1)时,依然返回true,此时如果再次调用一次free(p1)的话,就是要释放一块没有被引用的地址,肯定会出问题 //指针变量和它所指向的内存块是两个不同的概念,释放了指针所指的内存空间,但是指针变量本身没有重置,造成释放的时候if(p!=NULL) //避免方法:定义指针的时候初始化成NULL,释放指针所指的内存空间后把指针重置为NULL }
22、向null地址copy数据时报错的问题
void main(){ char* p1=NULL;//定义p1是一个地址 初始化让p1指向了0x0000这个地址 strcpy(p1,"abcdefg");//将"abcdefg"复制到p1所指向的0x0000地址里面,但是0x0000操作系统正在使用,所以报0x0000地址访问冲突的bug }
23、间接赋值
从0级到1级指针
int getFileLen(int *p){ *p=40; } //将变量b写在参数中和写在函数体里面没有任何区别,只不过写在参数中具有对外的属性而已,对外属性也就是可以在函数调用的时候为其赋值 int getFileLen3(int b){ b=100; } void main(){ int a=10; int *p=NULL; a=20;//直接修改 p=&a; *p=30;//p的值是a的地址,*就是一把钥匙,通过地址找到了一块内存空间 间接的修改了a的值 }
从1级指针到2级指针
void main(){ char *p1=NULL; char **p2=NULL; p1=0x11; p2=0x22; //直接修改p1的值 p1=0x111; p2=&p1; //间接修改p1的值 p2里面就是p1的地址 *p2=100; }
将上面的间接修改抽成函数
void getMem(char **p2){ *p2=200;//间接赋值 p2是p1的地址 } void main(){ char *p1=NULL; char **p2=NULL; p2=&p1; getMem(&p1);//一级指针p1再取地址就是二级指针 }
设想一下如果通过一级指针来间接修改指针的话
void getMem2(char *p1){ p1=800; //这里改的只是p1这个变量本身的值,并没有改变p1指向的内存空间的值,因此getMem2执行完后p1的内存被回收,main函数里面的p1指向的内存里面存储的值该是多少还是多少 } void main(){ char *p1=NULL; getMem2(p1);//不会成功 因为函数里面的形参在函数运行结束后被回收了 }
24、二级指针的应用:给p1 p2初始化,使其指向对应的内存空间
void getMem3(char **myp1,int *mylen1,char **myp2,int *mylen2){ int ret=0; char *tmp1,*tmp2; tmp1=(char *)malloc(100); strcpy(tmp1,"112233"); *mylen1=strlen(tmp1); *myp1=tmp1; tmp2=(char *)malloc(100); strcpy(tmp2,"aabbcc"); *mylen2=strlen(tmp2); *myp2=tmp2; return ret; } void main(){ char *p1=NULL; int len1=0; char *p2=NULL; int len2=0; getMem3(&p1,&len1,&p2,&len2); }
25、可以通过指针间接赋值的3个条件
int main(){ //条件1 定义了两个变量 int a=10; int *p=NULL; //条件2 建立了关联 p=&a; //条件3 *p *p=40; }
26、间接赋值的应用场景
void main(){ //第一种 1 2 3这3个条件写在一个函数中 //第二种 12 写在一个函数里面 3单独写在另外一个函数里面 =>函数调用 //第三种 1单独写 23写在一块儿 =>抛砖 在C++里面会有,以后会讲 //第一种 char from[128]; char to[128]={0}; char *p1=from; char *p2=to; strcpy(buf,"12345678"); while(*p1!=‘\0‘){ *p2=*p1; p2++; p1++; } printf("%s",to); }
间接赋值推论
用一级指针形参间接修改零级指针(实参)的值
用二级指针形参间接修改一级指针(实参)的值
27、理解指针必须和内存四区概念相结合
主调函数可把堆区 栈区 全局数据内存地址传给被调函数
被调函数只能返回堆区 全局数据区
指针做函数参数,是有输入(在主调用函数中分配内存,给被调用函数使用)和输出(和输入相反)特性的
企业开发中经常看到形参中这样的注释
int getMem(char** myp1/*out*/, int* mylen1/*out*/, char** myp2/*out*/, int* mylen2/*out*/); //注释中的out就是为了标明myp1是输出的指针
28、应用指针必须和函数调用相结合(指针做函数参数),以下是接口模型
一级指针做输入 //通常用于将数组或字符串作为参数传进被调函数中进行处理 int showbuf(char* p) int showArray(int* array, int iNum) 一级指针做输出 int getLen(char* pFileName, int* pFileLen) 二级指针做输入 int main(int arc, char* arg[]) int showMatrix(int[3][4], int iLine) 二级指针做输出 int getData(char** data, int* dataLen); int getData_Free(void* data); int getData_Free(void** data); //避免野指针 三级指针做输出 int getFileAllLine(char*** content, int* pLine); int getFileAllLine_Free(char*** content, int* pLine);
29、字符串
C语言的字符串是以0结尾的字符串
C语言中没有字符串类型 通过字符数组来模拟字符串
字符串的内存分配 可以在堆上 栈上 全局区 很重要!!!
void main(){ //字符数组初始化 //不指定长度的 C编译器会自动求出元素的个数 char buf1[]={‘a‘,‘b‘,‘c‘,‘d‘};//没有以0结尾 不是字符串 只算是字符数组 //指定长度 char buf2[100]={‘a‘,‘a‘,‘a‘,‘a‘};//没有赋值的都是0 //char buf3[2]={‘a‘,‘a‘,‘a‘,‘a‘};//如果初始化的个数大于长度 编译会报错 //字符串来初始化字符数组 char buf3[]="abcd";//buf3作为字符数组的长度是5,后面会多一个0 //buf3作为字符串长度是4 int len=strlen(buf3);//4 得到的是字符串的长度 int len2=sizeof(buf3);//5 得到的是数组的长度 //数组是一种数据类型,本质是固定大小内存块的别名 }
void main(){ int i=0; char *p=NULL; char buf5[128]="abcdefg";//这个字符串是在全局区存储的 for(i=0;i<strlen(buf5);i++){ printf("%c",buf5[i]); } p=buf5;//buf5代表数组首元素的地址 for(i=0;i<strlen(buf5);i++){ printf("%c",*(p+i)); } }
[]和*来访问数组元素没区别
buf5[i] ==> buf5[0+i] ==> *(buf5+i)
buf5是一个常量指针 编译器设计成这样是要保证buf5所指向的内存空间安全释放
30、字符串一级指针内存模型
void main(){ char buf[20] = "aaaa"; //为变量buf分配了20个字节大小的内存 字符串"aaaa"是放在了全局区 char buf2[] = "bbbb"; //为变量buf2分配了5个字节大小的内存 字符串"bbbb"也是放在了全局区 char *p1 = "111111"; //为变量p1分配了4字节大小内存 字符串"111111"也是放在了全局区 char *p2 = malloc(100); //为变量p2分配了4字节大小内存 从堆中申请了100个字节内存,让p2指向该内存 strcpy(p2, "3333"); //将全局区的"3333"拷贝到p2所指向的堆内存中 }
31、字符串copy函数技术推演
//原来的做法 void main(){ char a[] = "i am a student"; char b[100]; int i; for(i = 0;*(a + i) != ‘\0‘;i++){ *(b + i) = *(a + i); } //此时字符串a末尾的\0并没有拷贝到b中,因此需要手动拷贝 b[i] = ‘\0‘; }
//现在的做法 void main(){ char* from = "aaaa"; char to[100]; copy_str(from, to); } void copy_str(char* from, char* to){ for(;*from != ‘\0‘; from++,to++){ *to = *from; } *to = ‘\0‘; }
//优化后 void copy_str2(char* from, char* to){ for(;*from != ‘\0‘;){ //先*to = *from 再to++,from++ *to++ = *from++; } *to = ‘\0‘; } //再次优化 void copy_str3(char* from, char* to){ while((*to = *from) != ‘\0‘){ from++; to++; } } //再次优化 void copy_str4(char* from, char* to){ while((*to++ = *from++) != ‘\0‘); } //再次优化 void copy_str5(char* from, char* to){ //0是假 while(*to++ = *from++); }
//最好应该判断一下from和to指向的内存是否合法 void copy_str6(char* from, char* to){ if(to == NULL || from == NULL){ return -1; } while(*to++ = *from++); }
此时如果在main函数中想printf一下from字符串,发现打印不出来了,这是因为from已经指向‘\0‘了,因此打印不出来,应该在被调用函数中开两个变量把from和to缓冲一下
void copy_str4(char* from, char* to){ char* tmpfrom = from; char* tmpto = to; if(tmpfrom == NULL || tmpto == NULL){ return -1; } while(*to++ = *from++); }
上面的案例说明了我们不能轻易改变形参的值,要通过缓冲变量把形参接过来
指针初始化时如果没有合适的值必须初始化为NULL,指针在用完后也要赋值NULL
32、strstr-while do while模型
void main(){ int ncount = 0; char* p = "abcd111122abcd33333abcdabcd"; //求字符串abcd出现的次数 do{ // 返回字符串p中的字串"abcd"的位置 p = strstr(p, "abcd"); if(p != NULL){ ncount++; p += strlen("abcd"); }else{ break; } }while(*p != ‘\0‘); }
//将上述需求封装到函数中(以下代码为自己所写) void main(){ char* str = "abcd111122abcd33333abcdabcd"; char* substr = "abcd"; int len = 0; get_substr_times(str, substr, &len); } int get_substr_times(char* str, char* substr, int* times){ char* tmp_str = str; char* tmp_substr = substr; char* p = NULL; if(tmp_str == NULL || tmp_substr == NULL){ return -1; } while((p = strstr(tmp_str, tmp_substr)) != NULL){ *times++; p += sizeof(tmp_substr); if(*p == ‘\0‘){ break; } } return 0; }
33、两头堵模型:两个指针,一个指向字符串开头,另一个指向字符串结尾
求非空格的长度
void main(){ char *p = " abcdefg "; int i,j = 0; int ncount; i = 0; j = strlen(p) - 1; while(isspace(p[i]) && p[i] != ‘\0‘){ i++; } while(isspace(p[j]) && p[j] != ‘\0‘){ j--; } ncount = j - i + 1; }
封装到函数中:
void getCount(char* str, int* pCount){ char *p = str; int i,j = 0; if(str == NULL || pCount == NULL){ return -1; } i = 0; j = strlen(p) - 1; while(isspace(p[i]) && p[i] != ‘\0‘){ i++; } while(isspace(p[j]) && p[j] != ‘\0‘){ j--; } *pCount = j - i + 1; }
去除字符串前后空格
void trimSpace(char* str,char* newStr){ char *p = str; int i,j = 0; if(str == NULL || pCount == NULL){ return -1; } i = 0; j = strlen(p) - 1; while(isspace(p[i]) && p[i] != ‘\0‘){ i++; } while(isspace(p[j]) && p[j] != ‘\0‘){ j--; } strncpy(newStr, str, j - i + 1); newStr[j - i + 1] = ‘\0‘; }
优化:不传newStr
void main(){ // 由于传进来的字符串" abcdefg "存储在全局区,是不可以修改的,因此str在修改的时候报错 //char* str = " abcdefg "; // 因此要将内存分配在临时区 char str[1024] = " abcdefg "; trimSpace(str); } void trimSpace(char* str){ char *p = str; int i,j = 0; if(str == NULL || pCount == NULL){ return -1; } i = 0; j = strlen(p) - 1; while(isspace(p[i]) && p[i] != ‘\0‘){ i++; } while(isspace(p[j]) && p[j] != ‘\0‘){ j--; } // 如果传进来的字符串" abcdefg "存储在全局区,是不可以修改的,因此str在修改的时候报错 strncpy(str, str + i, j - i + 1); newStr[j - i + 1] = ‘\0‘; }
逆序模型
void main(){ char buf[] = "abcdefg"; int length = strlen(buf); char* p1 = buf; char* p2 = buf + length - 1; while(p1 < p2){ char c = *p1; *p1 = *p2; *p2 = c; ++p1; --p2; } }
抽到函数中
void main(){ char buf[] = "abcdefg"; reverse(buf); } int reverse(char* buf){ // 要先定义 int length; char* p1; char* p2; // 再使用 length = strlen(buf); p1 = buf; p2 = buf + length - 1; while(p1 < p2){ char c = *p1; *p1 = *p2; *p2 = c; ++p1; --p2; } }
通过递归翻转字符串
//栈模型是先入后出,因此可以先让各个字符依次入栈,再让各个字符依次出栈即可 //可以将逆序的结果存入全局变量 //递归的重点: //参数的入栈模型 //函数嵌套调用返回流程 char global_buf[100]; void main(){ char buf[] = "abcdefg"; memset(global_buf, 0, sizeof(global_buf)); reverse(buf); } void reverse(char* buf){ if(buf == NULL){ return; } //递归结束的条件 if(*buf == ‘\0‘){ return; } reverse(p + 1); //printf("%c", *p); //存到全局变量global_buf中 //strncpy(global_buf, p, 1); //把p所指向的字符串处的后面1个字符拷贝到global_buf中去 strncat(global_buf, p, 1); //把p所指向的字符串处的后面1个字符拼接到global_buf后面去 }
全局变量在多线程中会遇到问题,因此我们要考虑如何用递归修改一个局部变量
void main(){ char buf[]; char mybuf[1024]; buf = "abcdefg"; mybuf[1024] = {0}; reverse(buf, mybuf); } void reverse(char* buf, char* bufresult){ if(buf == NULL){ return; } //递归结束的条件 if(*buf == ‘\0‘){ return; } reverse(p + 1, bufresult); strncat(bufresult, p, 1); }
读取键值对
int getKeyByValue(char* keyvaluebuf, char* keybuf, char* valuebuf){ int ret = 0; char* p = NULL; if(keyvaluebuf == NULL || keybuf == NULL || valuebuf == NULL){ return -1; } //1、查找keybuf是不是在母串中 p = keyvaluebuf; //初始化辅助指针变量 p = strstr(p, keybuf); if(p == NULL){ return -1; } //让辅助指针变量重新到达下一次检索的条件 p = p + strlen(keybuf); //2、看有没有=号 p = strstr(p, "="); if(p == NULL){ return -1; } //让辅助指针变量重新到达下一次检索的条件 p = p + strlen("="); //3、在=号后面去除空格 ret = trimSpace(p, valuebuf); if(ret != 0){ printf("%d", ret); return ret; } return ret; } int main(){ int ret = 0; char buf[1024] = {0}; char* keyandvalue = "key2 = value2 key3 = value3 key4 = value4"; char* key2 = "key2"; ret = getKeyByValue(keyandvalue, key2, buf); if(ret != 0){ printf(ret); } }
34、一级指针容易犯错误的地方
(1)、初始化及在调用时所做的判断
char* buf = NULL; //这是对buf本身,即指针所处的内存本身进行初始化
char* buf = "abc"; //这是对buf指向的内存空间进行初始化,buf将指向全局区的一块空间
char buf[1024] = {0}; //这是对buf指向的内存空间进行初始化,buf将指向栈区的一块空间
指针传入函数后在使用之前需要先判断其是否合法,需要先判断指针是否为NULL,而不是指针指向的内存空间的值是否为NULL,即以下写法为错误写法:
if(*buf == NULL){
//...
}
如果buf传进来的是NULL,*NULL直接宕机
(2)、越界
char* buf[3] = "abc"; //错误
char* buf[4] = "abc"; //正确
35、二级指针案例:求文件中两段话长度
//求文件中两段话的长度 int getMem(char** p1, int* len1, char** p2, int* len2){ char* tmp1 = NULL; char* tmp2 = NULL; tmp1 = (char*)malloc(100); if(tmp1 == NULL){ return -1; } strcpy(tmp1, "abcdefg"); *len1 = strlen(tmp1); *p1 = tmp1; tmp2 = (char*)malloc(100); if(tmp2 == NULL){ return -2; } strcpy(tmp2, "abcdefg"); *len2 = strlen(tmp1); *p2 = tmp2; return 0; } //为了避免野指针的出现,传入二级指针时释放内存空间后还要把二级指针指向的地址空间的值赋值为NULL int getMemFree(char** p1){ char* tmp = NULL; if(p1 == NULL){ return -1; } tmp = *p1; free(*p1); *p1 = NULL; //释放完指针变量所指的内存空间之后再将实参修改成NULL return 0; } //传入一级指针时只需要释放内存空间即可 int getMemFree0(char* p1){ if(p1 == NULL){ return -1; } free(p1); //p1 = NULL; //在此将p1置为NULL没有任何意义,因为p1是一个形参,p1仅仅是栈空间的一个变量,函数调用结束之后p1内存被回收 return 0; } int main(){ char* p1 = NULL; int len1 = 0; char* p2 = NULL; int len2 = 0; getMem(&p1, &len1, &p2, &len2); getMemFree(&p1); getMemFree(&p2); //如果采用传入一级指针的方式释放: //getMemFree0(p1); //在被调用函数中 把p1所指向的内存释放掉,但是实参p1不能被修改成NULL,因此p1依然是野指针 //getMemFree0(p2); //这样释放完后p1 p2依然是野指针 }
36、指针做输出时由被调函数分配内存,指针做输入时由主调函数分配内存
37、二级指针做输入的三种模型
二级指针做输入第一种模型
void main(){ int i = 0; int j = 0; int num = 0; char* tmp = NULL; // 数组 数组中的内一个元素装的是指针 指针数组 // 注意与char* myArray的区分,char* myArray中的myArray变量存储的是一个字符串的首地址 // char* myArray[]中的myArray变量存储多个字符串首地址的集合 char* myArray[] = {"aaa", "ccc", "bbb"}; //打印 num = sizeof(myArray) / sizeof(myArray[0]); for(i = 0; i < num; i++){ // 以下两种方式打印效果一样 printf("%s", myArray[i]); printf("%s", *(myArray + i)); } //排序 for(i = 0; i < num; i++){ for(j = i; j < num; j++){ if(strcmp(myArray[i], myArray[j])){ // 注意:交换的是数组元素,也就是交换指针的值 tmp = myArray[i]; myArray[i] = myArray[j]; myArray[j] = tmp; } } } }
将上述打印和排序封装成函数
void printMyArray(char** myArray, int num){ int i = 0; for(i = 0; i < num; i++){ // 以下两种方式打印效果一样 printf("%s", myArray[i]); printf("%s", *(myArray + i)); } } void sortMyArray(char** myArray, int num){ int i = 0; int j = 0; char* tmp = NULL; for(i = 0; i < num; i++){ for(j = i; j < num; j++){ if(strcmp(myArray[i], myArray[j])){ // 注意:交换的是数组元素,也就是交换指针的值,并不是改变指针指向的值 tmp = myArray[i]; myArray[i] = myArray[j]; myArray[j] = tmp; } } } } void main(){ int num = 0; char* myArray[] = {"aaa", "ccc", "bbb"}; num = sizeof(myArray) / sizeof(myArray[0]); printMyArray(myArray, num); sortMyArray(myArray, num); }
二级指针做输入第二种模型
void main(){ int i = 0; int j = 0; int num = 4; char tmpBuf[30]; char myBuf[30]; char myArray[10][30] = {"aaa", "bbb", "ccc", "ddd"}; // 打印 for(i = 0; i < num; i++){ printf("%s", myArray[i]); } //排序 for(i = 0; i < num; i++){ for(j = i + 1; j < num; j++){ if(strcmp(myArray[i], myArray[j]) > 0){ strcpy(tmpBuf, myArray[i]); //交换的是内存块 strcpy(myArray[i], myArray[j]); strcpy(myArray[j], tmpBuf); } } } }
封装到函数中
void printMyArray_err(char** myArray, int num){ int i = 0; for(i = 0; i < num; i++){ printf("%s", *(myArray + i)); } } void printMyArray(char myArray[10][30], int num){ int i = 0; for(i = 0; i < num; i++){ printf("%s", *(myArray + i)); } } // 交换的是内存块,不是指针的指向 void sortMyArray(char myArray[10][30], int num){ int i = 0; int j = 0; char tmpBuf[30]; for(i = 0; i < num; i++){ for(j = i + 1; j < num; j++){ if(strcmp(myArray[i], myArray[j]) > 0){ strcpy(tmpBuf, myArray[i]); //交换的是内存块 strcpy(myArray[i], myArray[j]); strcpy(myArray[j], tmpBuf); } } } } void main(){ int num = 4; char myBuf[30]; // myArray:编译器只会关心:有10行,每行30列,这就解释了第二种内存模型 // 多维数组myArray + 1,每加1会往后走30字节,即步长是30字节 char myArray[10][30] = {"aaa", "bbb", "ccc", "ddd"}; // 打印 调用了上一种内存模型,会down掉 // 问题的本质是两种内存模型中的myArray + 1不一样,即指针的步长不一样,指针所指向的内存空间的数据类不一样 printMyArray_err(myArray, num); // 正确 printMyArray(myArray, num); // 排序 sortMyArray(myArray, num); // 用第一种内存模型求myArray的大小 { // 对二级指针常量len1求大小,相当于求其指向的内存空间的大小——300字节 10行30列 int len1 = sizeof(myArray); // myArray第一个元素是一级指针,相当于求其指向的内存空间(第一个元素)的大小,10行30列,每一行占30字节 int len2 = sizeof(myArray[0]); // 求二维数组有多少行 int size = len1/len2; printf("len1:%d, len2:%d, size:%d", len1, len2, size); } }
二级指针做输入第三种模型
void main(){ int i = 0; int j = 0; char** p2 = NULL; int num = 5; char* tmp; char tmpbuf[100]; p2 = (char**)malloc(sizeof(char*) * num); for(i = 0; i < num; i++){ //相当于char buf[100]; p2[i] = (char*)malloc(sizeof(char) * 100); sprintf(p[i], "%d%d%d", i + 1, i + 1, i + 1); } for(i = 0; i < num; i++){ printf("%s", p2[i]); } // 排序 通过交换指针的方式来排序 for(i = 0; i < num; i++){ for(j = i + 1; j < num; j++){ if(strcmp(p2[i], p2[j]) > 0){ tmp = p2[i]; p2[i] = p2[j]; p2[j] = tmp; } } } // 排序 通过交换指针指向的内存空间的数据来排序 for(i = 0; i < num; i++){ for(j = i; j < num; j++){ if(strcmp(p2[i], p2[j]) > 0){ // 老师写的 strcpy(tmpbuf, p2[i]); strcpy(p2[i], p2[j]); strcpy(p2[j], tmpbuf); // 我写的 // *tmpbuf = *p2[i]; // *p2[i] = *p2[j]; // *p2[j] = *tmpbuf; } } } // 释放内层的内存 后申请的先释放 for(i = 0; i < num; i++){ if(p2[i] != NULL){ free(p2[i]); p2[i] = NULL; } } // 释放外层的内存 先申请的后释放 if(p2 != NULL){ free(p2); } }
将上述程序改为指针做函数参数的
char** getMem(int num){ int i = 0; char** p2 = NULL; p2 = (char**)malloc(sizeof(char*) * num); if(p2 == NULL){ return NULL; } for(i = 0; i < num; i++){ //相当于char buf[100]; p2[i] = (char*)malloc(sizeof(char) * 100); sprintf(p2[i], "%d%d%d", i + 1, i + 1, i + 1); } return p2; } void printMyArray03(char** myArray, int num){ int i = 0; for(i = 0; i < num; i++){ // printf("%s", myArray[i]); printf("%s", *(myArray + i)); } } void sortMyArray03(char** p2, int num){}{ int i = 0; int j = 0; char* tmp; char tmpbuf[100]; // 排序 通过交换指针的方式来排序 for(i = 0; i < num; i++){ for(j = i + 1; j < num; j++){ if(strcmp(p2[i], p2[j]) > 0){ tmp = p2[i]; p2[i] = p2[j]; p2[j] = tmp; } } } // 排序 通过交换指针指向的内存空间的数据来排序 for(i = 0; i < num; i++){ for(j = i; j < num; j++){ if(strcmp(p2[i], p2[j]) > 0){ // 老师写的 strcpy(tmpbuf, p2[i]); strcpy(p2[i], p2[j]); strcpy(p2[j], tmpbuf); // 我写的 // *tmpbuf = *p2[i]; // *p2[i] = *p2[j]; // *p2[j] = *tmpbuf; } } } } void getMemFree03(char** p2, int num){ int i = 0; // 释放内层的内存 后申请的先释放 for(i = 0; i < num; i++){ if(p2[i] != NULL){ free(p2[i]); p2[i] = NULL; } } // 释放外层的内存 先申请的后释放 if(p2 != NULL){ free(p2); } } void main(){ int i = 0; int j = 0; char** p2 = NULL; int num = 5; char* tmp; char tmpbuf[100]; p2 = getMem(num); printMyArray03(p2, num); sortMyArray03(p2, num); }
38、三种模型内存表示
void main(){ int i = 0; // 第一种:指针数组 char* p1[] = {"123", "456", "789"}; // 第二种:二维数组 char p2[3][4] = {"123", "456", "789"}; // 第三种:手动分配二维内存 char** p3 = (char**)malloc(3 * sizeof(char*)); for(i = 0; i < 3; i++){ p3[i] = (char*)malloc(10 * sizeof(char)); sprintf(p3[i], "%d%d%d", i, i, i); } }
39、多级指针
int getMem52(char*** p3, int num){ int i = 0; char** p2 = NULL; //注:是判断p3,而不是判断p3指向的内存空间的值,即不是判断*p3,*p3是可以为NULL的 if(p3 == NULL){ return -1; } p2 = (char**)malloc(sizeof(char*) * num); if(p2 == NULL){ return NULL; } for(i = 0; i < num; i++){ //相当于char buf[100]; p2[i] = (char*)malloc(sizeof(char) * 100); sprintf(p2[i], "%d%d%d", i + 1, i + 1, i + 1); } *p3 = p2; } void getMemFree03(char*** p3, int num){ char** tmp = NULL; if(p3 == NULL){ return -1; } tmp = *p3; for(i = 0; i < num; i++){ free(tmp[i]); } free(tmp); *p3 = NULL; } void main(){ char** p2 = NULL; getMem52(&p2, 3); getMemFree(&p2, 3); }