做网站建设 个体经营 小微企业,潮阳建设局网站,网页美工设计工作内容,如何给自己网站做外链1、字符指针
#xff08;1#xff09;字符指针的普通用法
char a A;
char* pa a;但是一般来说字符指针很少这么用……更多是拿来存储一个字符串 #xff08;2#xff09;字符串的两种存储以及区别
现在有了两种存储数组的方法 ①一个是使用char类型数组存储②另外…1、字符指针
1字符指针的普通用法
char a A;
char* pa a;但是一般来说字符指针很少这么用……更多是拿来存储一个字符串 2字符串的两种存储以及区别
现在有了两种存储数组的方法 ①一个是使用char类型数组存储②另外一个是使用指向char类型的指针存储数组的首元素地址将其当成字符串的“标记”
#include stdio.h
int main()
{char str1[] hello word.;char str2[] hello word.;const char *str3 hello word.;//将字符串的首字母h的地址存储在str3里面const char *str4 hello word.;if(str1 str2)printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n);if(str3 str4)printf(str3 and str4 are same\n);elseprintf(str3 and str4 are not same\n);return 0;
}两个数组存储不同的字符串尽管他们的内容是相同的两个字符指针都指向内容相同的字符串 这意味着str3和str4指向的是同一个常量字符串C/C会把常量字符串存储到一个单独的内存区域当几个指针指向同一串字符串时他们实际是指向同一块内存。但是用相同的常量字符串去初始化不同数组时数组会对常量字符串进行拷贝开辟出不同的内存块所以str1!str2但是str3str4 3存储多个字符串----字符指针数组
int main()
{char* arr[3] { abcd, cdf, jiuh };int i 0;for(i 0; i 3; i){printf(%s\n, arr[i]);}return 0;
}2、指针数组
比较简单直接写下code就可以
char* arr[4];//存储了4个char*指针
char** arr[10];//存储了10个char**指针3、数组指针
1数组的基础概念
数组名字有两种情况如下code
//复习数组的名字含义
#include stdio.h
int main()
{int arr[10] { 0 };printf(%p\n, arr);//数组名字就是首元素地址printf(%p\n, arr[0]);//取出了首元素地址printf(%p\n, arr);//1、取出来整个数组的地址尽管arr和arr[0]的值一样但是意义不一样类型不一样前者是数组类型int[10]后者是整型类型intprintf(%zd\n, sizeof(arr));//2、这里的arr依旧是整个数组return 0;
}①sizeof(数组名)这里的数组名表示整个数组计算的是整个数组的大小 ②数组名这里取出来的是整个数组地址尽管它的值和数组的首元素地址相同两者是有区别的 最大的区别就在于int arr[10]声明后arr是首元素地址指针类型是int*这是个数组指针。而能存放arr这个地址的指针类型是int(*)[10]。这点在对两种指针进行/-整数的时候会更加明显因为指针会根据指向的类型对地址值进行增加 数组指针后面会讲 2数组指针的定义
数组指针也是指针
int (*p)[10];//p是一个指向一维整型数组的指针该数组内含10个int类型注意[]和()具有相同的优先级结合性是从左向右结合。并且优先级都比*高。 3数组指针的使用
//第一种使用方法
int main()
{int arr[10] {1, 2, 3, 4, 5};int (*p)[10] arr;return 0;
}语法逻辑没毛病可以但是一般不会这么使用//第二种使用方法
#include stdio.h
void print_arr1(int arr[3][5], int row, int col)
{int i 0;for(i0; irow; i){for(j0; jcol; j){printf(%d , arr[i][j]);}printf(\n);}
}void print_arr2(int (*arr)[5], int row, int col)
{int i 0;for(i0; irow; i){for(j0; jcol; j){printf(%d , arr[i][j]);}printf(\n);}
}int main()
{int arr[3][5] {1,2,3,4,5,6,7,8,9,10};//数组名arr表示首元素的地址//但是二维数组的首元素是二维数组的第一行指向这一行的指针类型可以写成int [][5]或者int (*)[5]//所以这里传递的arr其实相当于第一行的地址是一维数组的地址print_arr1(arr, 3, 5);print_arr2(arr, 3, 5);return 0;
}4更多数组指针的解读
int arr[5];//单纯是个整型数组int* parr1[10];//单纯是一个指针数组每一个指针都指向一个intint (*parr2)[10];//数组指针该指针指向一个包含10个int元素的数组int (*parr3[10])[5];//指针数组可以思考成“int(*)[5] parr3[10]”与int arr[5]是类似的很明显这是一个包含10个元素的数组每个元素都int(*)[5]这种类型的指针这种指针指向一包含5个元素的数组4、数组传参和指针传参
1一维数组传参
#include stdio.h
void test1(int arr[])//可以这么写
{//code
}
void test1(int arr[10])//10写与不写都行无所谓C会将它忽略
{//code
}
void test1(int *arr)//可以的传过来的arr1的拷贝是一个int元素地址
{//code
}void test2(int *arr[20])//20写与不写都行C依旧会将它忽略
{//code
}
void test2(int **arr)//也可以传过来的arr2的拷贝是一个int*元素的地址
{//code
}int main()
{int arr[10] {0};int *arr2[20] {0};test(arr1);test2(arr2);
}2二维数组传参
void test(int arr[3][5])//可以使用不过3会被忽略
{//code
}
void test(int arr[][])//不可以使用5必须留下来
{//code
}
void test(int arr[][5])//可以使用
{//code
}
//二维数组传参函数形参的设计只能省略第一个[]的数字。
//对一个二维数组可以不知道有多少行但是必须知道一行多少元素。void test(int *arr)//不行类型不匹配拷贝过来的参数是一个指向一维数组的指针而这个参数仅仅是一个一维指针
{//code
}
void test(int* arr[5])//不行这是一个指针数组根本没有关系
{//code
}
void test(int (*arr)[5])//可以这么写指针类型匹配了
{//code
}
void test(int **arr)//不行指针类型不匹配
{//code
}int main()
{int arr[3][5] {0};test(arr);
}//使用二维数组指针深刻理解
void print(int(*p)[20], int x, int y)
{//二维数组的名字就是首元素的地址其地址就是第一行数组的地址也就是指向一维数组类型的指针因此不能写形参为int**//又因为一维数组的整体地址arr和其首元素地址arr[0]起始位置相同故从值来看是一样的但是两者的指针类型完全不同for (int i 0; i x; i){for (int j 0; j y; j){printf(%d, *(* (p i) j));//得到数组名利用i得到每一行的数组名利用j得到某一行的每一列的元素地址}}
}
int main()
{int arr[10][20] { {1, 2, 3}, {2, 3, 4} };print(arr, 10, 20);return 0;
}3一级指针传参
#include stdio.h
void print(int *p, int sz)
{int i 0;for(i0; isz; i){printf(%d\n, *(pi));}
}
int main()
{int arr[10] {1,2,3,4,5,6,7,8,9};int *p arr;int sz sizeof(arr)/sizeof(arr[0]);//一级指针p传给函数print(p, sz);return 0;
}4二级指针传参
#include stdio.h
void test(int** ptr)
{printf(num %d\n, **ptr);
}
int main()
{int n 10;int*p n;int **pp p;test(pp);test(p);return 0;
}5总结
指针的类型一定要匹配好尽管类型不匹配的时候依旧可以进行传参因为所有地址在同一个平台都是一样大小的只是存储一个地址理论上用什么类型的指针都可以存储但是在后续使用指针的时候解引用指针就会发生错误指针会根据指针类型访问不同大小的内存
5. 函数指针
整型指针int*字符指针char*指向数组int arr[10]的数组指针int (*p)[10]指向函数function()的函数指针int (*pf)(int, int) function或者int (*pf)(int, int) function其中函数function()是一个有两个int参数返回值为int的函数
1函数指针 对于函数function()其函数指针类型为【返回值 (*指针名) (参数类型的列表)】 若想使用这个函数就要进行解引用使用【(* pf)(参数列表)】或者【pf(参数列表)】都可以。编译器在处理的时候没有 * 也行但是要用 * 就一定要加括号 尽管这样很矛盾但是调用函数指针时使用*pf和pf是一样的即函数名函数的地址而(函数名)函数的地址若是理解pf为指针则*pf变成函数名字*pf()就相当于function()若是理解pf为函数名则pf本身就是函数的名字pf()就相当于function()C允许这两种写法认为两种都合理
//例子
char* test(int c, float* pf)
{//某些代码
}
int main()
{char* (*pt)(int, float*)pf test;test(参数列表)return 0;
}2有关函数指针的一些有趣的代码
//代码1
(*( void (*)() )0)();//从最里面开始理解void(*)()是一个指向“返回值为空参数列表为空”函数的函数指针
//然后将0的int类型强制转化为函数指针类型于是0成了一个函数指针类型的地址
//再解引用0这个地址得到0地址处的函数然后使用这个函数//代码2
void (* signal(int , void(*)(int)) )(int);//等价代码如下
//typedef void(*pfun_t)(int);//注意pfun_t是一个和void(*)(int)同类型名将pfun_t放在*旁边是为了指明pfun_t是一个指针而已这只是语法形式要求
//pfun_t signal(int, pfun_t);//因此有一个简化代码的技巧就是使用typedef这两段代码来自于《C陷阱与缺陷》是本出名的C语言书籍
6. 函数指针数组
1使用例子
#include stdio.h
int add(int a, int b)
{return a b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a*b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input 1;int ret 0;do{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);switch (input){case 1:printf(输入操作数);scanf(%d %d, x, y);ret add(x, y);printf(ret %d\n, ret);break;case 2:printf(输入操作数);scanf(%d %d, x, y);ret sub(x, y);printf(ret %d\n, ret);break;case 3:printf(输入操作数);scanf(%d %d, x, y);ret mul(x, y);printf(ret %d\n, ret);break;case 4:printf(输入操作数);scanf(%d %d, x, y);ret div(x, y);printf(ret %d\n, ret);break;case 0:printf(退出程序\n);breark;default:printf(选择错误\n);break;}} while (input);return 0
}2改良后
#include stdio.h
int add(int a, int b)
{return a b;
}
int sub(int a, int b)
{return a - b;
}
int mul(int a, int b)
{return a*b;
}
int div(int a, int b)
{return a / b;
}
int main()
{int x, y;int input 1;int ret 0;int(*p[5])(int x, int y) { 0, add, sub, mul, div }; //转移表即函数指针数组这里的0或者写NULL起到占位的作用理论上放什么都行只要后续处理好就行…while (input){printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);if ((input 4) (input 1)){printf(输入操作数);scanf(%d %d, x, y);ret (*p[input])(x, y);}else{printf(输入有误\n);}printf(ret %d\n, ret);}return 0;
}
//当计算器后续需要加入更多的运算函数时那么使用开关语句就会显得冗长但是使用函数指针数组就不会有这个问题这将会大大缩减代码。但是使用函数指针也具有有缺点它只能存放同样函数签名函数签名定义了函数的输入和输出即函数签名参数返回值的函数3转移表的概念
像类似上面使用函数指针数组的方法就叫做转移表具有一种跳转使用函数的效果。即“函数指针数组”“转移表”
7. 指向函数指针数组的指针
void test(const char* str)
{printf(%s\n, str);
}
int main()
{//声明函数指针pfun并且进行初始化void (*pfun)(const char*) test;//声明一个函数指针的数组pfunArrvoid (*pfunArr[5])(const char* str) { pfun };pfunArr[0] test;//指向函数指针数组pfunArr的指针ppfunArrvoid (*(*ppfunArr)[5])(const char*) pfunArr;return 0;
}
//*代表ppfunArr是指针[]代表这个指针指向一个内含5元素的数组而每个元素的类型都是void (*)(const char*)
//因此按照运算符的顺序来解读是比较快的 8. 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针地址作为参数传递给另外一个函数当这个指针被用来调用其所指向的函数时我们就说这是回调函数。回调函数不是由该函数的实现方式直接调用而是在特定的事件或条件发生时由另外一方调用的用于对该事件或条件进行响应
1例子一使用qsort
①前要C库函数qsort能对数组进行排序
void qsort(void *base,//指向了待排序数组的第一个元素 size_t nitems,//排序的元素个数size_t size,//每个元素的大小单位是字节int (*compar)(const void*, const void*)//指向一个函数这个函数可以比较两个元素的大小);//其底层是使用快速排序的方法来排序的依靠compar指向的不同函数内部不同的比较可以解决不同类型的数据快速排序②使用qsort对数组进行排序
#include stdio.h
#include stdlib.h
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{return ( *(int*)p1 - *(int*)p2 );//注意不能直接解引用void指针另外如果倒过来就是逆序输出了
}
int main()
{int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i 0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i 0; i sizeof(arr) / sizeof(arr[0]); i){printf( %d , arr[i]);}printf(\n);return 0;
}2例子二模拟实现类似qsort函数的bubble函数底层采用冒泡函数
#include stdio.h
int int_cmp(const void * p1, const void * p2)//其中一个比较函数这个是整型比较是用户决定这个函数应该如何编写
{return (*(int*)p1 - *(int*)p2);//不过注意void不能直接解引用
}void _swap(void *p1, void * p2, int size)//其中一个排序方法这个是排序是冒泡排序可以由开发者决定底层排序的方法在qsort中使用的底层函数是快排
{int i 0;for (i 0; i size; i)//之所以这么做是因为没有办法预测有多少个字节只能通过一个一个字节进行交换最后所有字节进行交换即两个数据进行了交换{char tmp *((char*)p1 i);*((char*)p1 i) *((char*)p2 i);*((char*)p2 i) tmp;}
}void bubble(void *base, int count, int size, int(*cmp )(void*, void*))//相当于qsort但是实现逻辑不仅仅是快速排序内部的_swap函数也可能是其他的排序算法cmp函数可能比较不同类型的数据。 注意base是void*类型写int*会写死的只能限定于整型
{int i 0;int j 0;for (i 0; i count - 1; i){int flag 1;//①优化代码若是没有交换就说明不需要经过排序就是有序的了for (j 0; j count - i - 1; j){if (cmp ((char*) base j * size, (char*)base (j 1) * size) 0)//比较函数这里改成(char*)就可以利用size适应不同的字节{flag 0;_swap((char*)base j * size, (char*)base (j 1) * size, size);//排序函数这里改成(char*)就可以利用size适应不同的字节}if(flag 1){break;}}}
}int main()
{int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };//char *arr[] {aaaa,dddd,cccc,bbbb};int i 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i 0; i sizeof(arr) / sizeof(arr[0]); i){printf( %d , arr[i]);}printf(\n);return 0;
}注意我们不是在模拟qsort函数而是作一个类似qsort函数的“冒泡排序通用的bubble函数”