C的实用笔记29——函数指针(通过指针引用函数)

Posted lzh201864031

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C的实用笔记29——函数指针(通过指针引用函数)相关的知识,希望对你有一定的参考价值。

1.函数名就是地址

1、知识点:程序中定义的每一个函数,在编译时,编译系统会为函数在内存的代码区当中分配一段存储空间,这段存储空间的起始地址(又称为入口地址)称为这个函数的指针。

2.定义一个函数指针指向函数名

1、函数指针的概念:

                                                    

 2、正确的定义方式:

  1. 函数指针也讲究指针的类型。这里的类型是返回值的类型,也就是要函数的类型和函数指针的类型一致,这里的类型关注两点:①函数的返回值类型和函数指针前面类型是否一致、②函数的形参列表的参数类型和函数指针的形参列表参数类型是否一致。
    int getData(int data1, int data2);
    int (*p)(int data1, int data2);

  2. 见怪不怪:见到了不要怕,因为关心的是操作数的类型,而不是操作数的名字,所以可以不写操作数名字
    int getData(int data1, int data2);
    int (*p)(int, int);

3、函数指针为啥这样写?(通用的类型判别方法):

  1. 函数指针首先它得是一个指针,所以用小括号把*p括起来是为了先让修饰符*和p结合,确保p是一个指针;其次,关心的是这个指针指向的函数中的形参列表,以及返回值类型。
    正确的写法:
    int (*p)(int data1, int data2);//函数指针,指针指向的函数有两个int类型形参,返回值是int类型数据
    错误的写法:
    int *p(int data1, int data2);  //声明了一个函数p,该函数的返回值是int *类型的数据。

  2. 适用于变量、指针、函数的类型判别方法:地位从高到底
    1. 找到变量名,若没有变量名,找到最里面的结构
    2. 向右看,读出看到的东西,不要跳过括号
    3. 向左看,读出看到的东西,不要跳过括号
    4. 若有括号,跳出第一层括号
    5. 重复上述过程,直到读出最终的类型

3.通过函数指针间接调用函数

1、知识点:与变量的调用方法方法一样,函数也是有两种调用方法

  1. 通过函数名直接调用:
    定义函数: int getData(int data1, int data2)...
    通过函数名直接调用: getData(1, 2);

  2. 通过函数地址间接调用:思考为啥这么写?
    定义函数: int getData(int data1, int data2)...
    通过函数地址间接调用: (*&getData)(1, 2);

2、标准写法:(*p)(实际参数列表);

  1. 例子1:间接调用无返回值无参函数
    #include <stdio.h>
    void printWelcome()
    
        puts("Welcome");
    
    int main(int argc, char const *argv[])
    
        void (*p)();
        p = printWelcome;
        (*p)();     			//间接调用
        return 0;
    

  2. 例子2:间接调用有返回值有参函数
    #include <stdio.h>
    int inCData(int data)
    
        return ++data;
    
    int main(int argc, char const *argv[])
    
        int (*p)(int data);
        p = inCData;
        printf("%d\\n", (*p)(4));     //间接调用
        return 0;
    

3、函数指针间接调用时,为什么要加小括号?:

  1. 知识点:因为( )运算符的优先级高于 * 运算符,辨析如下写法
    int inCData(int data);	//函数声明
    int (*p)(int data) = inCData;	//函数指针变量
    写法一(正确):(*p)(4);   //运算符*先和函数指针p结合,取出p指向的地址中的内容,*p等价于inCData
    写法二(错误):  *p(4);   //函数指针p先和(4)结合,显然出错

4.函数指针的好用之处(函数的形参可接收多个API)

1、相同类型的函数都可 以作为一个函数的参数,但根据程序运行过程的不同情况,只调相应的函数:类似于java的接口。

练习1:

  1. 思路:switch选择语句、几个相同类型函数共用一个函数指针。
    f1. 封装求最大值的API:int getMax(int data1, int data2);
    	f1.1 用三目运算符返回最大值
    f2. 封装求最小值的API:int getMin(int data1, int data2);
    	f2.1 用三目运算符返回最小值
    f3. 封装求和的API:int getSum(int data1, int data2);
    	f3.1 返回两数之和
    Handler1. 封装数据处理的API:int dataHandler(int data1, int data2, int (*pfunc)(int data1, int data2)); //其中前两个是操作数,最后一个是运算法则
    	Handler1.1 间接调用函数指针pfunc指向的函数,并将操作数data1和data2传递进去
        Handler1.2 返回调用结果ret
    1. 定义并初始化两个数a和b
    2. 定义一个函数指针:int (*pfunc)(int data1, int data2); 它能够保存API1. API2. API3.中任意一个函数地址
    3. 输入1或2或3保存在cmd中
    4. switch选择语句,表达式是cmd。目标是确定pfunc的指向
    	4.1 case 1: 让函数指针pfunc指向API1.的地址
        	break;
    	4.2 case 2: 让函数指针pfunc指向API2.的地址
        	break;
    	4.3 case 3: 让函数指针pfunc指向API3.的地址
        	break;
    	4.4 default: 输入无效数字,用exit(-1);提前结束整个程序
    5. 调用Handler1. 将操作数a和b以及运算方法pfunc传递进去,返回值保存在结果ret中:
    	ret = dataHandler(a,b,pfunc);
    6. 打印结果ret

  2. 代码:
    #include <stdio.h>
    #include <stdlib.h>
    int getMax(int data1, int data2);
    int getMin(int data1, int data2);
    int getSum(int data1, int data2);
    int dataHandler(int data1, int data2, int (*pfunc)(int data1, int data2));
    int main(int argc, char const *argv[])
    
        int a = 10;
        int b = 20;
        int ret;
        int cmd;
        int (*pfunc)(int data1, int data2);         //三个函数共用一个函数指针
        puts("请输入1(取大值),2(取小值),3(求和)");
        scanf("%d", &cmd);
        switch(cmd)
            case 1:
                pfunc = getMax;
                break;
            case 2:
                pfunc = getMin;
                break;
            case 3:
                pfunc = getSum;
                break;
            default:
                puts("请重新输入");
                exit(-1);
        
        ret = dataHandler(a, b, pfunc);
        printf("%d\\n", ret);
        return 0;
    
    int getMax(int data1, int data2)
    
        return data1>data2 ? data1 : data2;
    
    int getMin(int data1, int data2)
    
        return data1<data2 ? data1 : data2;
    
    int getSum(int data1, int data2)
    
        return data1+data2;
    
    int dataHandler(int data1, int data2, int (*pfunc)(int data1, int data2))
    
        int ret;
        ret = (*pfunc)(data1, data2);
        return ret;
    

2、dataHandler函数的特点:

  1. dataHandler相当于决策者,根据程序运行的情况(这里是cmd的值)决定到底执行哪一个API(或者说运算法则),所以运算法则和操作数都要放在dataHandler的形参列表当中,然后在dataHandler的函数体中通过函数指针完成对操作数的操作。  
  2. 以往我们使用指针的思路是在main函数内部定义变量、数组、二维数组,把它们传给其他函数,在函数体内部通过指针的方法间接访问它们,希望借此来改变它们的值。如图:

3、代码心得:

  1. 记得exit(-1); 来提前结束程序,否则程序跑到最后,会延迟一下才退出程序,我们通过gdb软件测试,会发现实际上出现了Segmentation fault(段错误),原因是我们定义的函数指针是野指针,没有实际指向的内存地址。

4、线程举例:Linux系统中创建多线程的函数,它有个参数就是函数指针,用来接收线程函数的入口地址。创建线程成功后,有新的任务时,将函数地址传进去,就可以执行对应的线程函数。int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void*(*start_rtn)(void*), void *restrict arg);

        ↑                          ↑                                 ↑                                       ↑                               ↑  

 创造线程的函数     线程号的地址           线程的属性                     函数指针                普通指针

  1. 知识点:上述例子中,接收的线程函数的类型有两个要求,“ 函数的返回值必须是void * ”、“ 函数的参数必须是void * ”,这是啥意思?答:通用指针。

C语言学习笔记整理

一.数据在内存中的存储

1.1数据类型介绍

基本数据的类型:

类型的分类:
整形:
1.char也算到整型家族里面,因为字符在底层存储的时候,存储的是字符所对应的ASCII值(整数)
2.[int] 可以省略 unsigned (无符号) signed(有符号)
3.有符号signed的最高位为符号位,1表示负数,0表示正数, unsigned均为正数,最高位1是实数位,不为符号位。
浮点数类型:float double
构造类型:数组类型,结构体类型:struct 枚举类型:enum 联合类型:union
指针类型:int *pi,char *pc,float *pf,void *pv
空类型:void表示空类型,通常适用于函数的返回类型,函数的参数,指针类型。

1.2 整形在内存中的存储

计算机中的有符号数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示"负”,而数值位三种表示方法各不相同。
原码:直接将二进制按照正负数的形式翻译成二进制就可以。
反码:将原码的符号位不变,其他位依次按位取反就可以得到了。
补码:反码 + 1就得到补码。
整数有两种,有符号数和无符号数
有符号数-- - 符号位 + 数值位
正数 0 + 数值位
负数 1 + 数值位
举例说明:
int b = -1;
//10000000 00000000 00000000 00000001 - 原码
//11111111 11111111 11111111 11111110 - 反码
//11111111 11111111 11111111 11111111 - 补码
//ff ff ff ff - 十六进制显示形式
int a = 3;
//00000000 00000000 00000000 00000011 - 原码、反码、补码
//0000 0000 0000 0000 0000 0000 0000 0000 0000 0011
//0 0 0 0 0 0 0 0 0 3
// 00 00 00 03
对于整形来说:数据存放内存中其实存放的是补码

1.3大小端字节序介绍

大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位, , 保存在内存的高地址中。

1.4浮点型在内存中的存储解析

根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:
(-1) ^ S* M * 2 ^ E
(-1) ^ s表示符号位,当s = 0,V为正数﹔当s = 1,V为负数。
M表示有效数字,大于等于1, 小于2。
2 ^ E表示指数位。
IEEE754规定 : 对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

另外:IEEE754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。

二.指针详细介绍

1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2.指针的大小是固定的4\\8个字节(32位平台\\64位平台)
3.指针是有类型,指针的类型决定了指针的+ -整数的步长,指针解引用操作时候的权限
4.指针的运算

2.1字符指针

在指针的类型中我们知道有一种指针类型为字符指针char*;

将字符串的首地址放到指针中,通过指针可以找到该字符串(不是将字符串内容放到指针里面去)

栈区:局部变量,函数形参,函数调用
堆区:动态内存如malloc等申请使用
静态区:全局变量,static修饰的局部变量
常量区:常量字符串
常量区中的内容在整个程序的执行期间是不允许被修改的,且同一份常量字符串只会创建一份,不会重复创建存储。

// An highlighted block
#include<stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	char* p1 = "abcdef";
	char* p2 = "abcdef";
	if (arr1 == arr2)
	{
		printf("arr1 == arr2\\n");
	}
	else
	{
		printf("arr1 != arr2\\n");
	}
	if (p1 == p2)
	{
		printf("p1 == p2\\n");
	}
	else
	{
		printf("p1 != p2\\n");
	}
	return 0;
}

运行结果:arr1 != arr2 ;p1 == p2
创建数组需要开辟空间,数组arr1和arr2在内存空间所在位置是不同的,所以arr1 != arr2; char p1 = “abcdef”; char p2 = “abcdef”; "abcdef"是常量字符串,不能被修改,在内存空间所占位置固定,char * p1 = “abcdef”; 是将该常量字符串的首地址放到字符指针p1中,char* p2 = “abcdef”;
是将该常量字符串的首地址放到字符指针p2中。也就是说p1和p2存放都是常量字符串"abcdef"的首地址,所以 p1 ==p2。(注意:同样的常量字符串只会存一份,不会同时存两份,所以不会开辟不同的空间来存储。)**
const char p2 = “abcdef”; (指向常量字符串的指针最好加上const!)*

2.2指针数组

指针数组是一个存放指针的数组
例如:
int* arr1[5];
char* arr2[5];
double* arr3[5];

// An highlighted block
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[3] = { &a, &b, &c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *arr[i]);
	}
	return 0;
}

运行结果:10 20 30

2.3数组指针

数组指针是指针

// An highlighted block
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int* p = arr;// 数组名是首元素地址
	//数组指针 存放数组的指针变量
	int(*p)[10] = &arr;//(*p)代表p是指针变量
	//该指针指向了一个数组,数组10个元素,每个元素的类型是int
	//如果不用括号将*p括起来,写成int* p[10],这是指针数组
	return 0;
}

进一步介绍数组指针

// An highlighted block
//1:
char arr[5];
char(*pa)[5] = &arr;
//2:
char* ch[8];
char* (*pc)[8] = &ch;
//3:
int(*p)[10];
//解释: p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

// An highlighted block
int* p1; //p1+1 表示跳过一个int类型的长度,也就是4个字节
char* p2;//p2+1表示跳过一个char类型的长度,也就是1个字节
int(*p3)[10];//p3+1表示跳过一个具有10个整型长度的数组,也就是4*10=40个字节

对一个存放数组地址的指针进行解引用操作,找到的是这个数组,也就是这个数组的数组名,数组名这时候又表示数组首元素地址!

  • (p + i):相当于拿到了一行,相当于这一行的数组名
    (*p + i)[j] <===> ((p + i) + j)
// An highlighted block
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
		//方式一:通过指针找到与首元素偏移i个元素的地址,
		//再对齐解引用操作,找到这个元素
		printf("%d ", *(arr + i));
		//方式二:既然可以将arr赋值给p,说明arr与p等价
		//那么就可以直接用arr替代p进行相应的解引用操作
		printf("%d ", arr[i]);
		//方式三:通过数组名+[下标]访问数组元素
		//即arr+[下标i]访问下标为i的元素,也就是第i+1个元素
		printf("%d ", p[i]);
		//方式四:既然arr与p等价,
		//那么也可以直接用p+[下标]的方式访问数组的元素

		//上述四种方式实际结果完全相同,实际上也可以互相转换使用
	}
	return 0;
}

总结:我们对一个数组指针变量进行解引用操作,比如int(*p)[10],得到的是一个数组,或者说是这个数组的数组名,而数组名又可以表示该数组首元素的地址。如果要找到该数组中的每一个元素,就需要对这个数组元素的地址进行解引用操作。简单点来说就是,对一个数组指针类型进行解引用操作,得到的还是地址,对这个地址在进行相应的解引用操作,才能得到数组中的具体的元素。

2.4数组传参和指针传参

// An highlighted block
#include <stdio.h>
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 main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

以上五种传参方式均ok
注意:一维数组传参可以传数组形式,也可以传指针形式,传数组形式的时候数组元素的个数可以不写,也可以写,传指针的时候要注意指针的类型,也就是指针指向什么类型的元素,比如说指针指向int类型元素,那么指针的类型就是int*

2.5函数指针

我们创建函数的时候,就会在内存中开辟一块空间,既然占用了内存空间,那就有对应的内存空间地址。
函数指针,顾名思义就是指向函数的指针。

注意:
& 函数名 和 函数名均表示函数的地址!
数组名 != &数组名
函数名 == &函数名
通过函数指针,我们可以找到函数,然后去调用这个函数。 函数指针是 &
函数名,而我们函数调用的时候可以直接使用函数名,那么这里通过函数指针调用函数也可以这样写:

2.6函数指针数组

把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢 ?
int (parr1[10])();
int
parr210;
int ()() parr3[10];
parr1 先和[]结合,说明parr1是数组。
数组的内容是什么呢 ?
是int(
)()类型的函数指针。

// An highlighted block
#include<stdio.h>
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];
	//需要一个数组,这个数组可以存放4个函数的地址---函数指针的数组
	int (*pa)(int, int) = Add;
	int (*p[4])(int, int) = { Add,Sub,Mul,Div };//函数指针的数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\\n", (*p[i])(2, 3));
	}
	return 0;
}

运行结果:

2.7指向函数指针数组的指针

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

// An highlighted block
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int arr[10] = { 0 };//数组
	int(*p)[10] = &arr;//数组指针
	char* arr1[5] = { 0 };//字符数组
	char* (*p1)[5] = &arr1;//字符数组指针
	int(*p2)(int, int) = Add;//&Add;//函数指针
	int(*p3[4])(int, int) = { 0 };//函数指针数组
	//去掉变量名p3和数组[4]剩下的就是数组的类型 int(*)(int,int)
	int(*(*p4)[4])(int, int) = &p3;//指向函数指针数组的指针
	//p4是一个数组指针,指针指向的数组有4个元素
	//每个元素的类型都是函数指针,函数指针类型是int(*)(int,int)

	return 0;
}

2.8回调函数

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

三.字符函数和字符串函数

C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。

strlen
求字符串长度的算法分析:strlen接收到字符串起始位置的地址时,比较该地址处的内容是否为’\\0’,若不为’\\0’, 字符串的长度 + 1。

函数介绍:strlen
size_t strlen(const char* str);
头文件:string.h
函数名:strlen
函数参数:str,参数类型是const char* ,即需要进行求字符串长度的起始地址
函数返回类型: size_t,size_t是unsigned int的类型重定义,是无符号整型。库函数使用size_t类型可能考虑的是字符串的长度不可能是负数,所以用了无符号类型size_t。
函数功能:计算字符串的长度
strcat
如果我们要将一个字符串的内容追加到另外一个字符串的末尾空间中时,需要使用字符串拷贝-- - strcat函数
函数介绍:strcat
char* strcat(char* destination,const char* source);
头文件:string.h
函数名:strcat
函数参数:
参数1:destination, 类型:char* ,表示将字符串追加的目的地位置
参数2:source,类型:char* ,表示被追加字符串的源地址起始位置。
函数返回类型: char*,实际上就是返回destination(目的地)的起始位置
函数功能:字符串追加

// An highlighted block
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest != NULL);
	assert(src != NULL);
	char* dest_start = dest;
	//1.找到目的空间中的'\\0'
	while (*dest != '\\0')//跳过不是'\\0'的字符
	{
		dest++;
	}
	//2.追加
	while (*dest++ = *src++)
	{
		;
	}
	return dest_start;
}
int main()
{
	char arr1[30] = "hello";
	char arr2[] = "world";
	my_strcat(arr1, arr2);//模拟实现strcat
	printf("%s", arr1);
	return 0;
}

strcmp
如果我们要比较两个字符串的是否相等,或者比较字符串大小,不能用操作符 == 来直接进行判断,而是需要用到字符串比较函数strcmp
strcmp函数进行字符串追加的算法分析:
函数介绍:strcmp int strcmp(const char* str1,const char* str2);
头文件:string.h
函数名:strcmp
函数参数:
参数1:str1, 类型:char* ,表示将进行比较的第一个字符串
参数2:参数2:str2, 类型:char* ,表示将进行比较的第二个字符串
函数返回类型: int, 返回两个字符串比较的结果
函数功能:字符串比较

// An highlighted block
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* p1, const char* p2)
{
	assert(p1 && p2);
	while (*p1 == *p2)
	{
		if (*p1 == '\\0')
		{
			return 0;
		}
		p1++;
		p2++;
	}

//方法一:vs实现的方式
//if (*p1 > *p2)
//{
//	return 1;
//}
//else
//{
//	return -1;
//}

//方法二:linux下gcc实现方式
   return *p1 - *p2;
}

int main()
{
	char* p1 = "abcdef";
	char* p2 = "abqwt";
	if (my_strcmp(p1, p2) > 0)
	{
		printf("%s > %s\\n", p1, p2);
	}
	else if (my_strcmp(p1, p2) == 0)
	{
		printf("%s = %s\\n", p1, p2);
	}
	else
	{
		printf("%s < %s\\n", p1, p2);
	}
	return 0;
}

strncpy
strncpy与strcpy相比较多了一个字母n,这个n代表的是需要拷贝字符的个数,也就是说strncpy需要关注拷贝字符的个数,而不是像strcpy那样关注’\\0’。
char* strncpy(char* destination,const char* source,size_t num);
头文件:string.h
函数名:strncpy
函数参数:
【参数1】destination,类型:char*,拷贝字符的目的地位置,即接收字符的起始位置
【参数2】source,类型:char* ,拷贝字符的源地址,即拷贝字符串的开始位置。
【参数3】num,类型size_t,拷贝字符的个数,用来控制拷贝字符的长度。
函数返回类型:char* ,返回接收字符的起始位置。
函数功能:指定个数的字符串拷贝

// An highlighted block
#include<stdio.h>
#include<assert.h>
//模拟实现strncpy
//方法一
//char* my_strncpy(char* dest, const char* src, size_t n)
//{
//	char* dest_start = dest;
//	while ((n > 0) && (*src != '\\0'))
//	{
//		*dest = *src;
//		dest++;
//		src++;
//		n--;
//	}
//	while (n > 0)
//	{
//		*dest = '\\0';
//		dest++;
//		n--;
//	}
//	return dest_start;
//}

//方法二
char* my_strncpy(char* dest, const char* src, size_t count)
//count比n更有实际意义
{
	assert(dest != NULL);//引用断言
	assert(src != NULL);
	char* start = dest;
	while (count && (*dest++ = *src++) != '\\0')
	{
		count--;
	}
	if (count)
	{
		while (count--)
		{
			*dest++ = '\\0';
		}
	}
	return start;
}

int main()
{
	char arr1[10] = "abcdefg";
	char arr2[] = "1234";
	size_t len = 0;
	scanf("%d", &len);
	my_strncpy(arr1, arr2, len);
	printf("%s", arr1);
	return 0;
}

strncat
strcat函数是字符串追加,在使用的时候以src的’\\0’作为追加结束标志,因此在使用strcat来追加一个字符串数组本身的时候,会因\\0被提前覆盖而无法追加成功。
strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\\0’。(这也是我们在追加的时候,不用关注原dest, src中’\\0’,仅需关注追加字符的个数的原因)
char* strncat(char* destination,const char* source,size_t num);
头文件:string.h
函数名:strncat
函数参数:
【参数1】destination,类型:char*,被追加字符的目的地位置,即接收追加字符的起始位置
【参数2】source,类型:char* ,追加字符的源地址,即追加字符串的开始位置。
【参数3】num,类型size_t,追加字符的个数,用来控制追加字符的长度。
函数返回类型:char* ,返回接收字符的起始位置。
函数功能:指定个数的字符串追加
具体算法图解:

// An highlighted block
#include<stdio.h>
#include<assert.h>
//方法一
//char* my_strncat

以上是关于C的实用笔记29——函数指针(通过指针引用函数)的主要内容,如果未能解决你的问题,请参考以下文章

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C 传递和指针

C语言学习笔记--函数与指针