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... "); }
通过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... "); }
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!='