C基础补充

1.结构体使用typedef起别名的2种方式

typedef起别名的作用类似kotlin的typealias, 由于结构体也是数据类型的一种,因此也可以起别名

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct Person
{
    char name[64];
    int age;
};
//方式一:
typedef struct Person MyPerson;

//方式二:在声明结构体的时候就定义别名
typedef struct Student
{
    char name[64];
    int age;
} MyStudent;

int main()
{
    MyPerson p = {"张三", 20};
    printf("p.name=%s,p.age=%d\n", p.name, p.age);

    MyStudent s = {"小明", 30};
    printf("s.name=%s,age=%d\n", s.name, s.age);

    return 0;
}

思考 char* p1,p2中p1和p2是同一类型吗?
其实,它们的类型是不一样的,p1是char *类型,而p2是char类型.
如果要变成同一种类型,可以这样干

typedef char *PCHAR;
int main()
{
    // 使用typedef定义的类型
    PCHAR p1, p2;
    
    // 或者这样也是定义2个char*类型
    char *p3, *p4;
}

2.void类型和void*类型的区别

#include <stdio.h>

// 1.void 修饰函数参数和函数返回
void test01(void)
{
    printf("%s", "hello world");
}

// 2.不能定义void类型变量
void test02()
{
    // void va1;//报错
}

int main()
{
    test01(); // hello world
}

3.struct成员不允许在定义时初始化

#include <stdio.h>

struct Person
{
    char name[30] = {0}; // 编译报错
    int age = 20; // 编译报错
    struct Person p; //编译报错,因为编译器不知道p要分配多少内存
    //编译报错,成员的初始化必须要等结构体本身初始化之后才行
    struct Person p = {"hello",20};
};

4.sizeof操作符的注意事项

sizeof是c语言中的一个操作符,类似++、–等等,sizeof能够告诉我们编译器为某一特定数据或者某一类型的数据在内存中分配空间的大小,大小以字节位单位.
基本语法:

sizeof(变量);
sizeof 变量;
sizeof(类型);

注意:

  • sizeof返回的占用空间大小是为这个变量开辟的大小,而不是它用到的空间大小.
  • sizeof的返回值类型是unsigned int; (无符号的数去减一个数结果还可能是无符号的,所以sizeof的返回值不要拿去做运算).
  • 要注意数组名和指针变量的区别,对数组名使用sizeof返回的是整个数组的大小,而对指针变量使用sizeof返回的是指针类型变量所占的大小,这个和操作系统的位数有关,32位占4个字节,64位占8个字节.

C语言规定当一个数组名作为函数的形参的时候,它就变成了指向数组首元素地址的指针变量了.
所以sizeof返回值就不在是数组的大小了,例如:

#include <stdio.h>

int calArraySize(int arr[]) //其实和int *arr没有区别了.
{
    return sizeof(arr); //这里其实已经把arr单做指针变量计算了
}

int main()
{
    int arr[] = {1, 2, 3, 4, 5, 6};
    printf("sizeof arr:%ld\n", sizeof arr);           //24, 数组大小等于元素个数*元素类型长度
    printf("sizeof pointer:%d\n", calArraySize(arr)); //8,数组作为函数参数会退化为指向数组首元素的指针
}

5.指针步长的操作

不同类型的指针+1偏移的字节数是不一样的,这个和数据类型有关,int*是4个字节,double*是8个字节, 这个就叫做步长.

#include <stdio.h>

int main()
{
    int * p;
    printf("%p\n",p); // 0x0
    printf("%p\n",p+1); // 0x4
}

思考:如何通过操作指针修改结构体中任意成员的值

struct Person
{
    char a;
    int b;
    char c;
    int d; //如何通过指针操作修改成员d的值
};
int main()
{
    // 思考: 如何通过指针操作修改p结构体中d成员的值?
    struct Person p = {'a', 100, 'b', 200};
}

我们都知道结构体是一种数据类型,它的大小是由里面的成员个数和种类决定的,所以结构体的指针的步长和结构体的大小有关,结构体类型的指针偏移一个单位,大小就是偏移一个结构体大小.

那么如果修改步长是不是就可以操作任意成员呢?
对的,我们可以通过强转指针类型为char*类型,这样步长就是1个字节了.根据结构体的内存对齐模式可知偏移12个字节就可以定位到成员d的首地址了,然后再强转成int*类型就可以得到d成员的指针类型了,然后就可以取*操作值了.
答案就是:

#include <stdio.h>

struct Person
{
    char a;
    int b;
    char c;
    int d; //如何通过指针操作修改成员d的值
};
int main()
{
    // 思考: 如何通过指针操作修改p结构体中d成员的值?
    struct Person p = {'a', 100, 'b', 200};

    //对p先取地址得到指针类型,然后强转char*类型,然后偏移12个字节,然后再强转成int*类型,然后再取*操作d成员的值
    *(int *)((char *)&p + 12) = 1000;
    printf("%d\n", p.d); // 1000
}

6.操作已回收的栈变量地址的问题

由于栈中的变量在出栈(离开作用域)后就会被回收,所有继续操作它是有问题的,例如:

#include <stdio.h>

int *myFun()
{
    int a = 10;
    return &a; // 编译器会有警告
}

int main()
{
    int *p = myFun(); //这里去接收a变量的地址是有问题的,因为a离开函数作用域就死掉了.
    return 0;
}

在这里插入图片描述

同样的再来看一个错误的例子:

#include <stdio.h>

char *getString()
{
    char str[] = "hello world";
    return str;
}

int main()
{
    char *s = getString(); //这里也是有问题的,因为str离开函数体后也死掉了,所以它指向的地址也是有问题的
    printf("s=%s\n", s);
    return 0;
}

在这里插入图片描述
可以看到上面2个都在编译时就提示了警告了, 如何解决呢?

很简单,只需要将栈变量指向的地址改成堆中分配的地址就可以了,这样当函数结束的时候虽然栈中的变量死了,但是它指向的堆的空间还是有效的,因为堆内存需要程序员手动释放才被回收,所以返回堆的地址是还可以继续使用.

7.形参不能修改实参的问题

由于函数的形参在函数体内怎么变化,它都无法影响到实参,所以想修改实参的数据,必须要传递实参的地址,否则会有问题,例如下面这段代码就是有问题的.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void change(char *c)
{
    char *temp = malloc(100);//分配100个字节的堆内存
    memset(temp, 0, 100); //重置为0
    strcpy(temp, "hello world");
    c = temp;
}

int main()
{
    char *p = NULL;
    change(p);
    printf("p=%s\n", p);
    free(p);
    return 0;
}

结果是
在这里插入图片描述
发现p指针赋值失败了,这是因为p是实参,而change函数内部改变的是形参c的值,而且这里还存在内存泄露的问题,因为形参c离开函数后就挂了,然后它之前在堆中申请的100个字节的空间还没有释放,而且没有任何指针指向这块区域.

解决方案如下:
将函数形参变成二级指针,实参p改为传递地址就OK了.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void change(char **c)
{
    char *temp = malloc(100); //分配100个字节的堆内存
    memset(temp, 0, 100);     //重置为0
    strcpy(temp, "hello world");
    *c = temp; //取1级指针后赋值为temp指向的堆空间地址
}

int main()
{
    char *p = NULL;
    change(&p); //这里传递指针p的地址
    printf("p=%s\n", p);
    free(p);
    return 0;
}

此时p就能正常赋值了.
在这里插入图片描述

8.全局变量或者函数必须要先声明再使用

每一个.c文件就是一个编译单元,C语言编译器是单独编译每一个.c文件的,所以编译器在编译当前的.c文件的时候,如果发现存在没有声明的变量或者函数就会报错,虽然这个变量或者函数是全局的, 但是因为你存在其他的.c文件,所以编译器无法感知, 因此使用前必须先声明一下,可以在当前使用的.c文件中声明,用extern关键字, 也可以在.h文件中声明,然后通过include包含进来.
提示:声明外部的全局变量可以在函数内声明,也可以在函数外声明.

  1. 什么叫做变量的声明?
    形如 extern int a; 这样的语句,也就是没有赋值的操作.

  2. 什么叫做函数的声明?
    形如 extern int add(int a, int b);这样的语句,也就是没有函数体的函数.

例如在test2.c文件内定义如下:

 int a = 100; 

 void changeA(){
     a = 200;
 }

然后在test.c内通过extern来使用test2的变量和函数

#include <stdio.h>

// 声明外部全局变量
extern int a;
// 声明外部全局函数
extern void changeA();

int main()
{   
    // 访问外部变量
    printf("before change a is %d\n", a);
    // 调用外部函数
    changeA();
    // 访问外部变量
    printf("after change  a is %d\n", a);
    return 0;
}

现在假设直接编译test.c的话会看到如下错误:
在这里插入图片描述
正确的编译姿势是如下:
在这里插入图片描述
需要将定义全局变量和全局函数的test2.c文件和test.c一起编译.

9.const全局变量和const局部变量的区别

1)如果const修饰的是全局变量,那么它是存在常量区,和字符串常量存放的地方一样,全局唯一, 是不能修改的.
2)如果const修饰的是局部变量,那么它在栈区,是可以被间接修改的.

不同的编译器对字符串常量的处理也不一样,有些编译器是可以通过指针更改字符串常量的值的, 这种编译器通常是相同的字符串存在2个不同的地址.而大多数编译器都会对字符串常量进行优化,相同的字符串只会存在一个,那么它们的地址就是一样的.这种编译器是不允许修改字符串常量的值的.

#include <stdio.h>

int main()
{
    char *p1 = "hello";
    char *p2 = "hello";
    printf("p1=%p\np2=%p\n", p1, p2); //这里输出的值是一样的."hello"字符串在常量区只存在一份
    return 0;
}

结果如下
在这里插入图片描述

10.宏函数与普通函数的区别

宏函数并不是真正的函数,但是在一些场景中它的效率要高于普通的函数,这是由于宏函数没有普通函数参数压栈/跳转/返回的开销, 可以提高程序效率.

宏定义只在定义的文件中起作用,无参数的宏定义称为宏常量,带参数的宏定义称为宏函数

注意:
宏名一般大写,以便和变量区别;
宏定义可以是常量,表达式等;
宏定义不是C语言,不需要在行末加分号
宏名有效范围从定义到本源文件结束
可以使用#undef命令终止宏定义的作用域
在宏定义,可以引用已定义的宏名
对应宏函数的参数,需要用括号括住每一个参数,并括住宏的整体定义,避免宏函数和其他数值进行运算时宏展开得到的运算结果不符合预期

#include <stdio.h>

// 定义宏函数
#define MYADD(x, y) ((x) + (y))
// 定义宏常量
#define MAX 1024

// 普通函数
int add(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 10;
    int b = 20;
    printf("a+b=%d\n", MYADD(a, b));
    return 0;
}

11.正确认识字符串及数组的首地址

注意:字符串常量名以及数组名指向的是首元素的地址而不是末尾元素的地址.

#include <stdio.h>

int main()
{
    char *a = "abcd";
    printf("%p\n", a);       // 指针变量名指向的是首元素的地址,0x104623fac
    printf("%p\n", &a[0]);   // 字符串第一个元素的地址:0x104623fac
    printf("%p\n", (a + 1)); // 指针+1后,对应的地址:0x104623fad
    printf("%p\n", &a[1]);   // 字符串第二个元素的地址:0x104623fad

    int b[] = {1, 2, 3, 4};
    printf("%p\n", b);       //数组名指向首元素的地址,0x16f6930a0
    printf("%p\n", &b[0]);   // 数组首元素的地址:0x16f6930a0
    printf("%p\n", (b + 1)); // 数组名+1也就是指针+1,对应地址:0x16f6930a4
    printf("%p\n", &b[1]);   //数组第二个元素的地址:0x16f6930a4
}

12.获取结构体成员的地址偏移量

由于结构体有内存对齐模式,所以自己算比较麻烦,我们可以借助函数来实现
使用offsetof函数, 需要引入stddef.h库

size_t offsetof(type, member);

参数1是数据类型
参数2是要查找的结构体成员的变量名
返回值是该成员的首地址距离结构体的起始地址的偏移量,单位字节

用法如下:

#include <stdio.h>
#include <stddef.h>
struct Person
{
    int a;
    char b;
    char c[64];
    int d;
};

int main()
{
    struct Person p = {10, 'a', "hello world", 100};

    printf("a=%lu\n", offsetof(struct Person, a)); // a=0
    printf("b=%lu\n", offsetof(struct Person, b)); // b=4
    printf("c=%lu\n", offsetof(struct Person, c)); // c=5
    printf("d=%lu\n", offsetof(struct Person, d)); // d=72
}

可以看到每个成员的首地址距离结构体的起始位置的偏移量都是不一样的.
那么如果我想获取到成员d的值就可以这样操作

//1.首先对p取地址并强转成char*,那么指针的步长就是1了,
//2.然后加上d成员的偏移量,这样就取到了d成员的首地址
//3.然后再强转成int*,那么就得到了d成员的首地址
//4.然后再取*就可以取出这个地址对应的值了,也就是d的值, 100
int d = * (int *)((char*)&p + offsetof(struct Person,d));
printf("d=%d\n",d);//100

13.字符串拷贝的几种方式

这里介绍3种方式进行字符串的拷贝

#include <stdio.h>

//方式1
void copy_string01(char *dest, char *source)
{
    for (int i = 0; source[i] != '\0'; i++)
    {
        dest[i] = source[i]; //[]方式赋值
    }
}

//方式2
void copy_string02(char *dest, char *source)
{
    while (*source)
    {
        *dest = *source; //指针取*方式赋值
        source++;
        dest++;
    }
}

//方式3
void copy_string03(char *dest, char *source)
{
    while (*dest++ == *source++); //指针取*方式赋值
}

int main()
{
    char source[] = "hello world";
    char dest[100] = {0};
    copy_string01(dest, source);
    //copy_string02(dest, source);
    //copy_string03(dest, source);

    printf("desc=%s\n", dest);
    return 0;
}

14.数组下标能否是负数

可以,把数组当做指针看待就可以了,例如p[-1], 其实编译器会理解成*(p-1);例如:

#include <stdio.h>
int main()
{
    int arr[3] = {1, 2, 3};
    int *p = arr;
    p += 1;            //移动一个步长单位
    int b = p[-1];     //编译器会理解成 *(p-1);
    printf("%d\n", b); //结果是1
    return 0;
}

15.选择排序

15.1 数组排序

选择排序的思想就是通过内循环查找最小值的下标,然后外循环进行交换位置(与约定的最小值进行交换)

#include <stdio.h>

void print_array(int *arr, int len)
{
    for (int i = 0; i < len; ++i)
    {
        printf("arr[%d]=%d\n", i, arr[i]);
    }
}

void select_sort(int *arr, int len)
{
    for (int i = 0; i < len; ++i)
    {
        int min = i; //记录最小值下标,默认是i
        for (int j = i + 1; j < len; j++)
        {
            if (arr[j] < arr[min])
            {
                min = j; //内循环更新最小值的下标
            }
        }
        if (min != i)
        {
            //交换最小值
            int temp = arr[min];
            arr[min] = arr[i];
            arr[i] = temp;
        }
    }
}

int main()
{
    int arr[] = {10, 3, 20, 15, 66,46, 50, 100,34};
    int len = sizeof(arr) / sizeof(arr[0]);
    select_sort(arr, len);
    print_array(arr, len);
    return 0;
}

15.2 字符串排序

#include <stdio.h>
#include <string.h>

void print_array(char **arr, int len)
{
    for (int i = 0; i < len; ++i)
    {
        printf("arr[%d]=%s\n", i, arr[i]);
    }
}

void select_sort(char **arr, int len) 
{
    for (int i = 0; i < len; ++i)
    {
        int min = i; //记录最小值下标
        for (int j = i + 1; j < len; j++)
        {
            if (strcmp(arr[j], arr[min]) < 0)
            {
                min = j; //内循环更新最小值的下标
            }
        }
        if (min != i)
        {
            //交换最小值
            char* temp = arr[min];
            arr[min] = arr[i];
            arr[i] = temp;
        }
    }
}

int main()
{
    char *arr[] = {"fff", "aaa", "ggg", "ppp", "rrr", "jjj", "ddd"};
    int len = sizeof(arr) / sizeof(char *);
    select_sort(arr, len);//指向数组首元素的指针是char**
    print_array(arr, len);
    return 0;
}

16.Windows下静态库的创建和使用

16.1 静态库的创建

a.在vs下创建一个新项目,选择已安装模板的"常规",在右边类型下选择"空项目",在名称和解决方案中输入staticlib,点击确定
在这里插入图片描述
b.在解决方案资源管理器头文件中添加mylib.h文件,在源文件中添加mylib.c文件(即实现文件)
c.在mylib.h文件中添加代码:

#pragma once

#ifdef __cplusplus
extern "C"{
#endif

	int myadd(int a, int b);

#ifdef __cplusplus
}
#endif

d.在mylib.c文件中添加如下代码

#include "mylib.h"

int myadd(int a, int b)
{
	return a + b;
}

e.配置项目属性,因为这是一个静态链接库,在解决方案资源管理器中找到staticlib项目,右键选择"属性"
在这里插入图片描述
然后在项目属性的"配置属性"下选择"常规",在其下的配置类型中选择"静态库(.lib)".在这里插入图片描述
f.在解决方案资源管理器中找到staticlib项目,右键选择"重写生成",然后会在Debug文件夹下会得到mylib.lib(对象文件库),将该.lib文件和相应头文件给用户,用户就可以使用该库里的函数了.
在这里插入图片描述
进入目录就可以看到生层的库了.
在这里插入图片描述

16.2 静态库的使用

方法一:配置项目属性

a.添加工程的头文件目录:工程--属性--配置属性---c/c++ -- 常规--附加包含目录:加上头文件存放目录
b.添加文件引用的lib静态库路径:工程---属性---配置属性- --链接器---常规---附加库目录:加上lib文件存放目录
c:然后添加工程引用lib文件名:工程---属性---配置属性---链接器---输入---附加依赖项:加上lib文件名

方法二:使用编译语句

#pragma comment(lib,"./mylib.lib")

方法三:添加工程中

把库的.lib文件和.h文件拷贝到需要添加依赖库的工程项目根目录下
然后切换到"解决方案视图"--->选择要添加lib的工程-->右键-->添加-->现有项-->选择lib文件-->确定

下面仅以方法三作为例子演示
新建使用库的工程testStaticLib
在这里插入图片描述
右键该项目,选择"在文件资源管理器中打开文件夹"
在这里插入图片描述
然后将静态库的.lib和.h文件拷贝到上面打开的项目目录下
在这里插入图片描述
然后回到vs,右键testStaticLib工程–>添加–>现有项
在这里插入图片描述
将.lib和.h文件选中–>添加.
在这里插入图片描述
添加后效果图如下:
在这里插入图片描述
开始测试,在testStaticLib工程中添加测试的源文件

#include "mylib.h" //包含库的头文件
#include <stdio.h>


int main()
{
	int sum = myadd(10, 20);
	printf("sum=%d\n", sum);

	system("pause");
	return 0;
}

这样看到结果,说明使用库成功了.
在这里插入图片描述

17.Windows下动态库的创建和使用

17.1 动态库的创建

a.创建一个新项目,在已安装模板中选择"常规",在右边选择"空项目",在名称和解决方案名称中输入mydll,点击确定.
b.在解决方案资源管理器的头文件中添加mydll.h文件,在源文件中添加mydll.c文件
在这里插入图片描述
c.在mydll.h文件中添加下面代码

#pragma once

#ifdef __cplusplus
extern "C"{
#endif
	//内部函数自己使用
	//void func(){}

	//外部函数(导出函数)给别人使用
	__declspec(dllexport) int getsum(int a, int b);

#ifdef __cplusplus
}
#endif

d.在mydll.c文件中添加实现代码

#include"mydll.h"

int getsum(int a, int b)
{
	return a + b;
}

e.右键项目–>属性–>修改项目配置属性为动态库(.dll)
在这里插入图片描述
f.右键项目–>重新生层
在这里插入图片描述
然后打开目录就可以看到生成的mydll.dll动态库文件和mydll.lib库文件
在这里插入图片描述

17.2 使用动态库

方式一:隐式调用

创建主程序TestDll,将mydll.h、mydll.dll、mydll.lib复制到TestDll工程目录下。
ps:头文件mydll.h不是必须的,只是C++中使用外部函数时需要先声明
在程序中指定链接引用链接库:#pragma comment(lib,"./mydll.lib")

方式二:显示调用

HEADLE hDll;//声明一个dll实例文件的句柄
hDll  = LoadLibrary("mydll.dll");//导入动态链接库
//获取导入函数的函数指针
MYFUNC getsum = (MYFUNC)GetProceAddress(hDll,"getsum");
int ret = getsum(10,20);

下面以方式一作为示例:
新建使用工程TestDll, 将mydll.h、mydll.dll、mydll.lib复制到TestDll工程目录下。
在这里插入图片描述
然后回到TestDll工程,在源文件中新建测试文件

#include "mydll.h" //导入头文件
#include<stdio.h>
#pragma comment(lib,"./mydll.lib") //指定使用的动态库

int main()
{
	int ret = getsum(10, 20);
	printf("ret=%d\n", ret);

	system("pause");
	return 0;
}

在这里插入图片描述

18.面向接口的编程

甲乙双方约定好需求的调用接口后就可以各自开发了.假设甲方是调用方,乙方是实现方.

需求:实现能够支持多个厂商的socket通信

甲乙双方约定的接口如下:

//初始化链接
void init_socketclient(void**handler);
//发送消息
void send_msg(void *handler,unsigned char *msg,int len);
//接收消息
void receive_msg(void *handler,unsigned char *msg,int *recvLen);
//关闭链接
void close_socketclient(void *handler);

甲方可以通过定义函数指针类型的方式提前写好调用的框架

#include<stdio.h>
#include<string.h>

//根据约定定义相应的函数指针类型
typedef void(*init_socket_client)(void**);
typedef void(*send_msg)(void*, unsigned char*, int len);
typedef void(*recv_msg)(void*, unsigned char*, int* recv_len);
typedef void(*close_socket_client)(void*);

//调用框架,参数都是函数指针类型
void socket_demo(init_socket_client init,
	send_msg sender,
	recv_msg receiver,
	close_socket_client closer)
{
	//下面通过函数指针执行函数
	
	//1.初始化
	void *handler = NULL;
	init(&handler);

	//2.发送数据
	char *buf = "hello world";
	sender(handler, buf, strlen(buf));
    printf("开始发送数据%s\n", buf);

	//3.接收数据
	char recv_buf[1024] = { 0 };
	int recv_len;
	receiver(handler, recv_buf, &recv_len);
	printf("接收到数据%s\n", recv_buf);

	//4.关闭连接
	closer(handler);
	
}

乙方则提供函数的声明和具体的实现
SocketImpl.h

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void init_socket_client_impl(void** handler);
void send_msg_impl(void* handler, unsigned char* msg, int len);
void recv_msg_impl(void* handler, unsigned char* msg, int* recv_len);
void close_socket_client_impl(void* handler);

SocketImpl.c

#include"SocketImpl.h"

//模拟数据的载体
struct Info
{
	char data[1024];
	int len;
};
//初始化
void init_socket_client_impl(void** handler)
{
	//实现方是知道具体的数据类型的
	struct Info* info = (struct Info*)malloc(sizeof(struct Info));
	//指针的间接复制,给handler赋值
	*handler = info;
}

//发送消息
void send_msg_impl(void* handler, unsigned char* msg, int len)
{
	if (NULL == handler)
		return;
	struct Info* info = (struct Info*)handler;
	//将数据保存到结构体中
	strncpy(info->data, msg, len);
	info->len = len;
}

//接收消息
void recv_msg_impl(void* handler, unsigned char* msg, int* recv_len)
{
	if (NULL == handler)
		return;
	struct Info* info = (struct Info*)handler;

	//将结构体的数据赋值给外部参数
	strncpy(msg, info->data, info->len);
	*recv_len = info->len;
}

//关闭
void close_socket_client_impl(void* handler)
{
	if (NULL != handler)
	{
		free(handler);
		handler = NULL;
	}
}

然后甲乙双方都完成后,乙方就可以提供函数的声明和实现给甲方使用了.甲方得到后,就可以将SocketImpl.h和SocketImpl.c文件拷贝到自己工程的根目录内.然后在vs工程右键项目添加–>现有项,将这2个文件选中添加即可.
在这里插入图片描述
然后在甲方的工程中调用之前写好的socket_demo框架,把具体的函数实现传递进去就可以使用了.当然使用前还需要包含头文件,完整代码如下:

#include<stdio.h>
#include<string.h>
#include "SocketImpl.h"  //包含乙方写好的头文件

//根据约定定义相应的函数指针类型
typedef void(*init_socket_client)(void**);
typedef void(*send_msg)(void*, unsigned char*, int len);
typedef void(*recv_msg)(void*, unsigned char*, int* recv_len);
typedef void(*close_socket_client)(void*);

//调用框架,参数都是函数指针类型
void socket_demo(init_socket_client init,
	send_msg sender,
	recv_msg receiver,
	close_socket_client closer)
{
	//下面通过函数指针执行函数

	//1.初始化
	void *handler = NULL;
	init(&handler);

	//2.发送数据
	char *buf = "hello world";
	sender(handler, buf, strlen(buf));
	printf("开始发送数据%s\n", buf);

	//3.接收数据
	char recv_buf[1024] = { 0 };
	int recv_len;
	receiver(handler, recv_buf, &recv_len);
	printf("接收到数据%s\n", recv_buf);

	//4.关闭连接
	closer(handler);

}

int main()
{
	//调用框架,传递乙方写好的函数实现
	socket_demo(init_socket_client_impl,
		send_msg_impl,
		recv_msg_impl,
		close_socket_client_impl);

	system("pause");
	return 0;
}

运行结果如下:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值