前言:本章主要内容是C指针进阶部分,旨在一次性讲透指针,主要以程序代码块形式讲解。有些难度,坚持住兄弟们!
char* ps = "hello";
char arr[] = "hello";
完成以上赋值后,进行如下输出,思考下输出结果?
printf("%c\n", *ps);
printf("%s\n", ps);
printf("%s\n", arr);
答案:分别输出h、hello、hello,第一个h是因为*ps指针指向的就是hello的首字符h,故输出h;后俩个输出本质上一样的,数组名arr和ps一样都是hello的首字符地址,故均输出hello。
思考下列程序输出结果是两个都相同呢?还是存在不同?(题选自剑指offer)
char str1[] = "hello";
char str2[] = "hello";
const char* str3 = "hello";
const char* str4 = "hello";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
答案:str1和str2不同,str3和str4相同。因为str1[]、str2[]两个数组分别创建了一块空间,分别放了hello,而数组名又代表首元素地址,str1和str2分别指向两个hello的首地址,这俩地址不同,故str1和str2不同;而str3和str4指向的hello是常量字符串的首元素地址,该字符串不能被修改,内存中只存一份,故str3和str4相同。
定义:本质上是数组,但数组中存放的是指针(地址)
int* arr[3];//存放整形指针的数组
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]));
}
解析:arr[i]内存放的分别是a、b、c的地址,想要打印出具体数字就需要对arr[i]使用解引用操作符*
int a[5] = {
1,2,3,4,5 };
int b[] = {
2,3,4,5,6 };
int c[] = {
3,4,5,6,7 };
int* arr[3] = {
a,b,c};
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));
printf("%d ", arr[i][j]);
}
printf("\n");
}
解析:int* arr[3] = {a,b,c};相当于int* arr[3] 里边三个指针类型的元素分别指向数组a、数组b、数组c的首地址。输出结果将分别输出数组a、b、c所有元素。
定义:是一种指针,是指向数组的指针
怎么取一个数组的地址,直接用数组名行不行?
int arr[10]={
1,2,3};
arr;
&arr;
int (*parr)[10] = &arr;
注释:“&arr”取出的是数组的地址,“arr”是数组首元素的地址,二者本质上不同,但是打印出来显示的值是一样的。parr就是一个数组指针,parr中存放的就是数组的地址。
思考下对于存放以下指针数组应该用什么样子的数组指针?
double* d[5];
应该用:
double* (*pd)[5] = &d;//ok pd就是一个数组指针
对于数组arr[]而言,arr与&arr意义完全是不一样的,以下代码感受以下:
int arr[10] = {
0};
int* p1 = arr;
int (*p2)[10] = &arr;
printf("%p\n", p1);
printf("%p\n", p1+1);
printf("%p\n", p2);
printf("%p\n", p2+1);
注释:p1是整形指针,+1会跳过四个字节,p2是一个数组指针,+1会跳过一个数组。
再提一次,数组名是数组首元素的地址,但是有2个例外:
- sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节
- &数组名 - 数组名表示整个数组,取出的是整个数组的地址
数组指针的数组遍历:
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
int (*pa)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*pa) + i));
}
但是多多少少有点麻烦,没必要用到数组指针,单纯使用指针int *pa=&arr就足够了。
二维数组通过数组指针传参:
void print2(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {
{
1,2,3,4,5},{
2,3,4,5,6},{
3,4,5,6,7 } };
print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
return 0;
}
解析:二维数组的数组名表示首元素的地址,而二维数组的首元素是:第一行。二维数组把每一行看成一个元素。int(*p)[5]是一个指向一维数组的指针,每个数组有5个元素,每个元素都是int类型。
int arr[5] 整型数组
*int parr1[10] 整型指针的数组
*int (parr2)[10] 数组指针,该指针能够指向一个数组,数组有10个元素,每个元素的类型是int
*int (parr3[10])[5] 一个存放数组指针的数组,能存放10个数组指针,每个数组指针指向一个数组,数组有5个元素,每个元素是int类型,图片拆解如下:
判断哪些传参类型可以?哪些不行?
答案:都正确,这里分析下最后一个二级指针来传递指针函数
判断哪些传参类型可以?哪些不行?
答案见下图(直接手写分析了):
i--;
if (i > sizeof(i))//
{
printf(">\n");
}
else
{
printf("<\n");
}
第一眼看是不是觉得一点难度都没有,i没有初始化默认是0,i–执行后i的数值是-1,sizeof(i)得到4,-1<4所以认为输出是<,但很遗憾,你成功被坑了。
解析:因为sizeof()得到的数据类型是无符号整型unsigned int,所以-1直接转换成无符号整形的形式,变成一个巨大的数字,所以结果是>。
void test(char* p)
{
}
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//p是一级指针
print(p, sz);
char ch = 'w';
char* p1 = &ch;
test(&ch);//char*
test(p1);
return 0;
}
解析:遇见函数形式参数类型为一级指针(比如char*)时要知道实际参数传递过来的是地址。
二级指针的3种传参方式,如下程序所示。
void test(int** p2)
{
**p2 = 20;
}
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
//怎么把二级指针进行传参呢?
test(ppa);
printf("%d\n", a);//
test(&pa);
printf("%d\n", a);//
int* arr[10] = {
0};
test(arr);
printf("%d\n", a);//
return 0;
}
解析:pa是一级指针,ppa是二级指针,test(ppa)把二级指针ppa进行传参;test(&pa)传一级指针变量的地址;test(arr)传存放一级指针的数组,传递了arr数组名,数组名代表首元素的地址,而arr数组每个元素都是int *类型,int *的地址相当于int * *,这样一来,符合咱们 void test(int ** p2)中形参的要求,也可以传递。
定义:存放函数地址的指针
int Add(int x, int y)
{
return x + y;
}
int main()
{
//&函数名 - 取到的就是函数的地址
//pf就是一个函数指针变量
int (*pf)(int, int) = &Add;
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
解释:函数名Add和&Add的效果是一样的。&函数名 - 取到的就是函数的地址;函数名-取到的也是函数的地址。
int main()
{
//pf就是一个函数指针变量
//int (*pf)(int, int) = &Add;
int (*pf)(int, int) = Add;//Add === pf
int ret1 = (*pf)(3, 5);//方式1
int ret2 = pf(3, 5);//方式2
int ret3 = Add(3, 5);//方式3
//int ret4 = * pf(3, 5);//err
printf("%d %d %d \n", ret1, ret2, ret3);
return 0;
}
解释:int ret1 = (*pf)(3, 5); int ret2 = pf(3, 5);int ret3 = Add(3, 5);三种调用方式效果一样,int ( *pf )(int, int) = Add;Add能把地址放进pf,说明两者一样的,Add==pf。方式1和方式2一模一样,那个解引用操作符 * 没有任何作用,就是一个摆设之所以放着 * 就是为了便于理解。
(*(void(*)())0)();
调用0地址处的函数
该函数无参,返回类型是void
- void(*)() - 函数指针类型
- (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
- (void()())0 - 对0地址进行了解引用操作
- ((void()())0)() - 调用0地址处的函数
void (* signal(int, void(*)(int) ) )(int);
- signal 和()先结合,说明signal是函数名
- signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
该函数指针,指向一个参数为int,返回类型是void的函数- signal函数的返回类型也是一个函数指针
该函数指针,指向一个参数为int,返回类型是void的函数
signal是一个函数的声明
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = Sub;
int (*pfArr[2])(int, int) = {
Add, Sub};//pfArr就是函数指针数组
return 0;
}
解析:int (*pfArr[2])(int, int) = {Add, Sub};函数指针数组,用于存放同类型的函数指针。
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;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int main()
{
int input = 0;
//计算器-计算整型变量的加、减、乘、除
//a&b a^b a|b a>>b a<b
do {
menu();
//pfArr就是函数指针数组
//转移表 - 《C和指针》
int (*pfArr[5])(int, int) = {
NULL, Add, Sub, Mul, Div };
int x = 0;
int y = 0;
int ret = 0;
printf("请选择:>");
scanf("%d", &input);//2
if (input >= 1 && input <= 4)
{
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
ret = (pfArr[input])(x, y);
printf("ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
定义:回调函数就是一个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时, 我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的- -方调用的,用于对该事件或条件进行响应。
考虑用回调函数简化计算器解法1
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;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int Calc(int (*pf)(int, int))
{
int x = 0;
int y = 0;
printf("请输入2个操作数>:");
scanf("%d %d", &x, &y);
return pf(x, y);
}
int main()
{
int input = 0;
//计算器-计算整型变量的加、减、乘、除
//a&b a^b a|b a>>b a<b
do {
menu();
int ret = 0;
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
ret = Calc(Add);
printf("ret = %d\n", ret);
break;
case 2:
ret = Calc(Sub);
printf("ret = %d\n", ret);
break;
case 3:
ret = Calc(Mul);//
printf("ret = %d\n", ret);
break;
case 4:
ret = Calc(Div);//
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误,重新选择!\n");
break;
}
} while (input);
return 0;
}
作用:排序任何类型的变量
为什么这里使用的都是void 指针?*
void * 指针是无具体类型的指针,可接受任意数据类型的指针,但不能直接进行加减整数等操作,并不能直接进行解引用。
因为要接收的数组类型我们不能确定,可能是整型,也可能是浮点型,所以只能用void* 来接收所有类型的元素。
并且下面我们在使用void*指针进行比较时,都会先进行强制类型转换。
当所计算数据类型为int时,如下所示即可。
#include
//qosrt函数的使用者得实现一个比较函数
int cmp1(const void * p1, const void * p2) //提供的是整型数组元素大小的比较方式
{
return (*( int *)p1 - *(int *) p2);
//强制类型转换为int*,说明比较的是整型元素
//p1,p2接收的是对应元素的地址
//如果返回值大于0,则两元素进行交换
}
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), cmp1);
//sizeof(arr) / sizeof(arr[0])即数组元素个数
//sizeof (int)即数组元素大小
for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
{
printf( "%d ", arr[i]);
}
printf("\n");
return 0;
}
当所计算数据类型为浮点数类型时,可修改为如下形式:
int cmp2(const void* elem1, const void* elem2)
{
return (*(float*)elem1 - *(float*)elem2);
}
当所计算数据类型为结构体类型时,可修改为如下形式:
int cmp3(const void* elem1, const void* elem2)
{
return (strcmp(((struct stu*)elem1)->name, ((struct stu*)elem2)->name));//比较结构体成员中的字符串类型
}
int cmp4(const void* elem1, const void* elem2)//比较结构体成员中的int类型
{
return (((struct stu*)elem1)->num - ((struct stu*)elem2)->num);
}
使用回调函数,模拟实现qsort(采用冒泡的方式)。
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
int cmp1(const void* p1, const void* p2) //提供的是整型数组元素大小的比较方式
{
return (*(int*)p1 - *(int*)p2);
}
int cmp2(const void* elem1,const void* elem2) //提供的是浮点型数组元素大小的比较方式
{
return (*(float*)elem1 - *(float*)elem2);
}
struct stu2 {
char name[20];
int age = 0;
};
int cmp3(const void* elem1, const void* elem2) //提供的是结构体中stu2中name成员的比较方式
{
return (strcmp(((struct stu2*)elem1)->name, ((struct stu2*)elem2)->name));
}
int cmp4(const void* elem1, const void* elem2)//提供的是结构体中stu2中age成员的比较方式
{
return (((struct stu2*)elem1)->age - ((struct stu2*)elem2)->age);
}
void customer_sort(void*base, int num, int width, int (*cmp)(const void*elem1,const void* elem2))
{
int i = 0;
for (; i < num - 1; i++)//确定排序趟数
{
for (int b = 0; b < num - 1 - i; b++)//每趟排序进行比较的次数
{
if (cmp(((char*)base + b * width), ((char*)base + (b + 1) * width)) > 0)
//如果cmp函数返回值大于0,则进行交换
{
char tmp = 0;
for (int j = 0; j < width; j++)
{
tmp = *((char*)base + b * width + j);
*((char*)base + b * width + j) = *((char*)base + (b + 1) * width + j);
*((char*)base + (b + 1) * width + j) = tmp;
}
}
}
}
}
int main()
{
int arr1[10] = {
2,4,6,8,1,3,9,5,7,10 };
float arr2[5] = {
3.0,5.0,1.0,7.0,8.0 };
int sz = sizeof(arr2) / sizeof(*arr2);
customer_sort(arr2, sz, sizeof(*arr2),cmp2);//如果要排序其他类型数组
//把cmp函数换成对应的就行了
for (int a = 0; a < sz; a++)
{
printf("%f ", arr2[a]);
}
return 0;
}
注释:
1.因为我们无法确定传进来的数组元素是啥类型,所以接受数组起始位置的指针使用的是空指针void* base,方便接收所有类型的指针,cmp函数的两个参数也是如此。
2.传递数组元素给cmp函数时,因为无法确定元素类型,所以指针往后走一步的大小也无法确定,所以只能用char* 对base进行强制类型转换,然后循环跳到下一个元素时,使用b * width,width表示对应元素类型的大小,b * width即跳过b * width个字节,找到对应元素的地址,(char * )base + 1 * width即为数组第二个元素,(char*)base + b*width即为第b+1个元素,下标为b,这样可以跳到对应位置,交换元素时,也需要按照这种方式来进行交换。并且交换也只能按一个字节一个字节的内容进行交换,故第三个for循环就是让两元素的每个字节都进行交换,循环次数也取决于width的大小,这样才能保证一个元素的所有字节都进行了交换。
sizeof(数组名) - 数组名表示整个数组的-计算的是整个数组的大小
&数组名 - 数组名表示整个数组,取出的是整个数组的地址
除此之外,所有的数组名都是数组首元素的地址
int a[] = {
1,2,3,4 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a + 0));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(a[1]));
printf("%d\n", sizeof(&a));
printf("%d\n", sizeof(* &a));
//&a -- int(*p)[4] = &a;
printf("%d\n", sizeof(&a + 1));
printf("%d\n", sizeof(&a[0]));
printf("%d\n", sizeof(&a[0] + 1));
答案:
char arr[] = {
'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//这里没有\0之分
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
答案:
char arr[] = {
'a','b','c','d','e','f' };
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
//printf("%d\n", strlen(*arr));
//printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
答案:
因为strlen结束标志是\0,所以前俩个找不到结束标志结果就是随机值;第三和第四个错了,strlen()应该传递的是地址,他们却传递了具体数值,把数值97当成具体地址;第五个传递过去依然会被认为是arr首地址,随机值;第六个跳过一个数组,也是随机值;第七个同理,从b的地址向后寻找\0,也是随机值。
char arr[] = "abcdef";
//[a b c d e f \0]
printf("%d\n", strlen(arr));
printf("%d\n", strlen(arr + 0));
//printf("%d\n", strlen(*arr));
//printf("%d\n", strlen(arr[1]));
printf("%d\n", strlen(&arr));
printf("%d\n", strlen(&arr + 1));
printf("%d\n", strlen(&arr[0] + 1));
答案:
char arr[] = "abcdef";
//[a b c d e f \0]
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr + 0));
printf("%d\n", sizeof(*arr));
printf("%d\n", sizeof(arr[1]));
printf("%d\n", sizeof(&arr));
printf("%d\n", sizeof(&arr + 1));
printf("%d\n", sizeof(&arr[0] + 1));
答案:
char* p = "abcdef";
printf("%d\n", sizeof(p));
printf("%d\n", sizeof(p + 1));
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(p[0]));
printf("%d\n", sizeof(&p));
printf("%d\n", sizeof(&p + 1));
printf("%d\n", sizeof(&p[0] + 1));
答案:4/8;4/8;1;1;4/8;4/8;4/8
char* p = "abcdef";
printf("%d\n", strlen(p));
printf("%d\n", strlen(p + 1));
printf("%d\n", strlen(*p));
printf("%d\n", strlen(p[0]));
printf("%d\n", strlen(&p));
printf("%d\n", strlen(&p + 1));
printf("%d\n", strlen(&p[0] + 1));
答案:
6;5;err;err;
第五个随机(对p取地址,向后去\0压根不知道结果);
第六个随机,与第五个同理;
5 。
int a[3][4] = {
0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(a[0][0]));
printf("%d\n", sizeof(a[0]));
printf("%d\n", sizeof(a[0] + 1));
printf("%d\n", sizeof(*(a[0] + 1)));
printf("%d\n", sizeof(a + 1));
printf("%d\n", sizeof(*(a + 1)));
printf("%d\n", sizeof(&a[0] + 1));
printf("%d\n", sizeof(*(&a[0] + 1)));
printf("%d\n", sizeof(*a));
printf("%d\n", sizeof(a[3]));
printf("%d\n", sizeof(a[-1]));
答案:
int a[5] = {
1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));
答案:
2;5
由于还没学习结构体,这里告知结构体的大小是20个字节。
假设p 的值为0x100000。 如下表达式的值分别为多少?
已知,结构体Test类型的变量大小是20个字节
提示:指针类型决定了指针的运算
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[2];
short sBa[4];
}* p;
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
答案:
int a[4] = {
1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
答案:
提示:坑爹的逗号表达式
int a[3][2] = {
(0, 1), (2, 3), (4, 5) };
int* p;
p = a[0];
printf("%d", p[0]);
答案:1
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
答案:
int aa[2][5] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
第一反应是5;1,误以为&aa+1是跳过一行,aa+1是跳过一个。然而实际上&aa+1跳过整个二维数组,aa+1跳过一行。
答案:10;5
char* a[] = {
"work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
答案:at
char* a[]是指针数组,数组每个元素都是char 类型,a[]中第一个元素是char ,那么指向其地址就应该是char
char* c[] = {
"ENTER","NEW","POINT","FIRST" };
char** cp[] = {
c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--* ++cpp + 3);
printf("%s\n", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
答案:
提示:只有第一步和第二步改变了cpp的指向,第三第四部均没有改变cpp的指向。
C指针进阶部分到此介绍结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。