//头文件函数声明
//防止头文件重复包含(相互包含陷入包含死循环)
#pragma once
//兼容C++编译器
//如果是C++编译器,按c标准编译
#ifdefine __cplusplus //C语言编译器中已有的宏,不是自定义宏
extern "C"
{
#endif
//接口函数
#ifdefine __cplusplus
extern "C"
}
#endif
//在a.cpp文件中调用b.c文件中函数,而又不想将b.c文件改成b.cpp文件,则需要在b.h文件加入上述代码,让其按照c标准编译
CS:客户端服务器
BS:浏览器和服务器(可以认为其是一种特殊的CS)
学习标准
第一套api接口
//初始化socket句柄的接口
int socket_client_init(void **handle);
//发送socket网络报文
int socket_client_send(void *handle, unsigned char *buf, int buf_len);
//接收socket网络报文
int socket_client_recv(void *handle, unsigned char *buf, int *buf_len);
//释放socket句柄资源
int destory_socket(void *handle);
第二套api接口
//初始化socket句柄的接口
int socket_client_init(void **handle);
//发送socket网络报文
int socket_client_send(void *handle, unsigned char *buf, int buf_len);
//接收socket网络报文
int socket_client_recv(void *handle, unsigned char **buf, int *buf_len);
//
socket_client_free(unsigned char **buf);
//释放socket句柄资源
int destory_socket(void **handle);
void类型
①函数参数为空,定义函数时,可以用void修饰;
②函数没有返回值;
③不能定义void类型的普通变量:void a;//err,无法确定类型,不同类型分配空间不一样(数据类型本质,固定内存块大小别名)
④可以定义void *变量: void * p;//合法,32位机器占4个字节,64位机器占8个字节
⑤void *p 万能指针,函数返回值,函数参数。
内存四区
//"abcdef"保存在字符串常量区
char *get_str1()
{
char *p ="abcdef";//p指向文字常量区
return p;
}
char *get_str2()
{
char str[] ="abcdef";//str空间为栈区,将文字常量区的内容拷贝到str空间,此函数调用完成后str所指向的内存区域被释放
return str;
}
char *get_str3()
{
char *tmp = (char *)malloc(100);//堆区,由程序员释放
if(tmp == NULL)
{
return NULL;
}
strcpy(tmp,"abcdef");
return tmp;
}
int main(void)
{
char *p1 =NULL;
char *p2 =NULL;
char *p3 =NULL;
p1=get_str1();
printf("%s\n",p1);
p2=get_str2();
//get_str2()运行完毕,str空间自动回收,str的空间内容未知,有可能保留之前的内容,也有可能是乱码
printf("%s\n",p2);
p3=get_str3();
if(p3!=NULL)
{
printf("%s\n",p3);
free(p3);//由程序员释放
p3=NULL;//养成良好习惯
}
return 0;
}
函数的调用模型:先进后出
栈的生长方向和内存的存放方向:栈的生长方向为由高到底,而内存以及堆的生长方向由底到高
程序验证
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *get_str1()
{
char *tmp = (char *)malloc(100);//堆区,由程序员释放
if(tmp == NULL)
{
return NULL;
}
strcpy(tmp,"abcdef");
return tmp;
}
char *get_str2()
{
char *tmp = (char *)malloc(100);//堆区,由程序员释放
if(tmp == NULL)
{
return NULL;
}
strcpy(tmp,"taylen");
return tmp;
}
int main()
{
//栈的生长方向
int a;
int b;
printf("栈的生长方向\n");
printf("&a: %p\n", &a);
printf("&b: %p\n", &b);
//堆的生长方向
char * p1=NULL;
char * p2=NULL;
p1 = get_str1();
p2 = get_str2();
printf("堆的生长方向\n");
printf("p1的地址:%p\n",p1);
printf("p1+1地址:%p\n",p1+1);
printf("p1的地址:%p\n",p1);
printf("p2的地址:%p\n",p2);
free(p1);
free(p2);
//内存的生长方向
char buf[4];
printf("内存的生长方向\n");
printf("buf的地址: %p\n", &buf[0]);
printf("buf+1地址: %p\n", &buf[1]);
return 0;
}
输出结果:
①栈区地址生长方向:地址由上往下递减
②堆区地址生长方向:地址由下往上递增
③数组buf,buf+1地址永远递增
指针易错点
①写内存时,一定要内存可写
char *buf1 = "abcdefg";//文字常量区,内存不可改
buf1[2] = '1';//err
char buf2[] = "abcdefg";/
buf2[2] = '1';//ok
②不允许向NULL和未知非法地址拷贝内存
指针强化
如何定义合适类型的指针变量:某个变量地址需要定义一个怎样类型的变量保存;在这个类型的基础上加一个*
int b;
int *q = &b;
int ** t =&q
如果想通过函数形参改变实参的值,必须传地址:
1、值传递,形参的任何修改不会影响到实参
2、地址传递,形参(通过*操作符号)的任何修改会影响到实参
主调函数与被调函数:
a)主调函数可把堆区、栈区、全局数据内存地址传给被调函数
b)被调函数只能返回堆区、全局数据
内存分配:
a)指针做函数参数,是输入与输出特性的(输入:主调函数分配内存;输出:被调函数分配内存)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void fun1(char *p /*in*/)
{
if(p == NULL)
{
return;
}
//给p指向的内存区域拷贝
strcpy(p,"PointerAsInput");
}
void fun2(char **p /*out*/,int *len)
{
if(p == NULL)
{
return;
}
char *tmp = (char *)malloc(100);
if(tmp == NULL)
{
return;
}
strcpy(p,"PointerAsOutput");
//简洁赋值
*p =tmp;
*len =strlen(tmp);
}
int main(void)
{
//输入,主调函数分配内存
char buf[100]={0};
fun1(buf);
printf("buf = %s\n",buf);
//输出,被调用函数分配内存,地址传递
char *p = NULL;
int len = 0;
fun2(&p);
if(p!=NULL)
{
printf("p = %s,len = %d\n",p,len);
free(p);
p=NULL;
}
system("pause");
return 0;
}
字符串
/*C语言没有字符串类型,通过字符数据模拟
C语言字符串,以字符'\0'结尾,数字0*/
//不指定⻓长度,C编译器会⾃自动帮程序员求元素的个数
//buf1是⼀一个数组 不是⼀一个以0结尾的字符串
char buf1[] = {'a', 'b', 'c', 'd'};
printf("buf1 = %s\n",buf1) //结果会是abcd+乱码
//用字符串初始化字符数组 ,常用
char buf2[] = "abcd"; //buf2 作为字符数组 有5个字节
// 作为字符串有 4个字节
int len = strlen(buf2);
printf("buf2字符的⻓长度:%d \n", len); //4
//buf3 作为数组 数组是⼀一种数据类型 本质(固定⼩小⼤大内存块的别名)
int size = sizeof(buf2); //
printf("buf2数组所占内存空间⼤大⼩小:%d \n", size); //5
指针和字符串数组名是否等价?
char buf[] = "abcdefg";
char *p =NULL;
p = buf;
p++;//没问题
buf++;//报错
/*buf只是一个常量,不能修改,主要是为了确保最终内存可以回收*/
const的使用
const声明的变量只能被读 ,且必须被初始化。但可以通过指针间接修改。
1)const声明变量为只读
const int a = 10;
a=100;//err
char buf[100]="abcd";
const char *p=buf;
char const *p=buf;//修饰*,指针指向能变,指针指向的内存不能变
//p[0]='1';//err
p = "123456";//ok
char *const p1 = buf;//修饰指针变量,指针指向的内存能变,指针指向不能变
//p1="123456";//err
p1[0]='1';//ok
const char* const p2=buf;//p2,只读
二级指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针 变量。也称为“二级指针”。
指针数组:
//指针数组,指针的数组,它是一个数组,每个元素都是char*
char *p[] = {"1111","0000","aaaa","bbbb"};
char **q = {"1111","0000","aaaa","bbbb"};//err
//只有在作为函数参数时两者可通用
二级指针做函数参数输出特性
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getMem(char *p)
{
p = (char *)malloc(sizeof(char) * 100);
if (p == NULL)
{
return -1;
}
//p = "abcdef";
strcpy(p, "abcdef");
printf("p = %s\n", p);
return 0;
}
int main(void)
{
char *p = NULL;
int ret = 0;
ret = getMem(p);
if (ret != 0)
{
printf("getMem err: %d\n", ret);
return ret;
}
printf("p = %s", p);
printf("\n");
system("pause");
return 0;
}
//输出结果
/*
p = abcdef
p = (null)
*/
上面的例子,在执行语句ret = getMem(p)时,只是值传递,拷贝了实参p的值赋值给形参p,即形参p的值一开始即为NULL,随后通过malloc分配了内存,并将该内存中填入“abcd”,但其并未与实参p有实际对应关系。相应的正确代码如下(地址传递,形参修改会影响到实参):
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getMem2(char **p)
{
if (p == NULL)
{
return -1;
}
char * tmp;
tmp = (char *)malloc(sizeof(char) * 100);
if (tmp == NULL)
{
return -2;
}
//p = "abcdef";
strcpy(tmp, "abcdef");
*p = tmp;
return 0;
}
int main(void)
{
char *p = NULL;
int ret = 0;
ret = getMem2(&p);
if (ret != 0)
{
printf("getMem err: %d\n", ret);
return ret;
}
printf("p = %s", p);
if (p != NULL)
{
free(p);
p = NULL;
}
printf("\n");
system("pause");
return 0;
}
对应的内存四区图如下: