征服C语言之指针的进阶

目录

前言

一.字符指针 

二.指针数组

三.数组指针

&数组名vs数组名

 数组指针的使用

四.数组参数,指针参数

一维数组传参

二维数组传参

一级指针传参

二级指针传参

 五.函数指针

六.函数指针数组

实现一个简单的计算器

七.指向函数指针数组的指针

八.回调函数

 模拟实现qsort函数

九.指针和数组笔试题解析

一维数组

字符数组

二维数组 

 十.指针笔试题


前言

指针的主题,我们在初级阶段的《指针》章节已经接触过了,我们知道了指针的概念: 1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。 2. 指针的大小是固定的 4/8 个字节( 32 位平台 /64 位平台)。 3. 指针是有类型,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限。 4. 指针的运算。 这个章节,我们继续探讨指针的高级主题。

一.字符指针 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	char ch = 'q';
	char * pc = &ch;

	char* ps = "hello bit";
	char arr[] = "hello bit";
	*ps = 'w';//err

	arr[0] = 'w';
	printf("%c\n", *ps);//h
	printf("%s\n", ps);//hello bit
	printf("%s\n", arr);//wello bit

	return 0;
}
  • char* ps = "hello bit";不是把字符串 hello bit放到字符指针 pstr 里了,而是把"hello bit"这个字符串的首字符的地址存储在了ps中
  • "hello bit"是一个常量字符串,所以不能被修改, 则*ps = 'w';这个语句就是错的
面试题:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char* str3 = "hello bit.";
    const char* str4 = "hello bit.";
    //*str3 = 'w';

    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");

    return 0;
}

征服C语言之指针的进阶征服C语言之指针的进阶

str1和str2不同,str3和str4相同 。
  • 这其实也很好理解"hello bit.",这是一个常量字符串,不能被修改,又因为str1和str2都是指向同一个常量字符串,自然也就不需要再开辟一段空间放相同的常量字符串
  • srt1和str2虽然数组的内容一样,但是str1和str2中的"hello bit."是可以被修改,所以开辟了2个不同数组存放"hello bit."

二.指针数组

什么是指针数组,简单的说就是一个数组,它的元素是指针,比如

int* arr1 [ 10 ]; // 整形指针的数组 char * arr2 [ 4 ]; // 一级字符指针的数组 char ** arr3 [ 5 ]; // 二级字符指针的数组

看代码

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
  •  上面的这段代码是使用了指针数组的,但是使用了之后,感觉很别扭,

看代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	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");
	}
	return 0;
}

征服C语言之指针的进阶

  • 这样使用指针数组,是不是感觉像是二维数组的访问

三.数组指针

什么是数组指针呢?简单的说就是一个指针,它指向的是一个数组,比如

int ( * p2 )[ 10 ];
  • 这里要注意:[]的优先级比*的优先级更高,如果写成 int *p2[10];则p2就变成了指针数组,所以我这里加了一个()

看代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int a = 10;
	int*pa = &a;
	char ch = 'w';
	char*pc = &ch;

	double* d[5];
	double* (*pd)[5] = &d;//ok pd就是一个数组指针

	int arr[10] = {1,2,3,4,5};
	int (*parr)[10] = &arr;//取出的是数组的地址 

	//parr就是一个数组指针 - 其中存放的是数组的地址
	//arr - 数组名是首元素的地址 - arr[0]的地址
	return 0;
}
  • 上面对pd和parr就是数组指针,一种指针,指向数组

看代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	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);
	return 0;
}

征服C语言之指针的进阶

  •  上面开始我就说了,指针加1,是由指针类型决定的,
  • p1是一个整形指针,加1跳过一个整形,即4个字节
  • p2是一个数组指针,加1跳过一个数组,即40个字节

&数组名vs数组名

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	int arr[10] = {0};
	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);
	return 0;
}

征服C语言之指针的进阶征服C语言之指针的进阶

  • 数组名是数组首元素的地址
  • 但是有2个例外:
    1. sizeof(数组名) - 数组名表示整个数组,计算的是整个数组大小,单位是字节
    2. &数组名 - 数组名表示整个数组,取出的是整个数组的地址

 数组指针的使用

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void print1(int arr[3][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 ", arr[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

//p是一个数组指针
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");
	}
	printf("\n");
}

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7 } };
	print1(arr, 3, 5);
	print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
	return 0;
}

 征服C语言之指针的进阶

  •  数组指针一般的应用场景都是在二维数组中,用在一维数组中感觉很别扭
  •  至于为什么能在二维数组中用,主要还是因为二维数组的数组名,相当于一维数组,这里我就不详细的说了,

四.数组参数,指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

一维数组传参

征服C语言之指针的进阶
  • 第一个test正确,传入数组,然后用数组接收,没问题
  • 第二个test正确,形参int arr[10]中的10可写,可不写,没问题
  • 第三个test正确,虽然传入的是数组,但是数组名是首元素的地址,用int *arr接收没有问题
  • 第一个test2正确,传入数组,然后用数组接收,没问题
  • 第二个test2正确,虽然传入的是数组,但是数组名是首元素的地址-->int *,用int **arr接收没有问题

二维数组传参

征服C语言之指针的进阶

  •  第一个test正确,传入数组,然后用数组接收,没问题
  •  第二个test错误,传入数组,然后用数组接收,但是形参int arr[][]不能省略列
  • 第三个test正确,传入数组,然后用数组接收,形参int arr[][5]可以省略行
  • 第四个test错误,虽然传入的是数组,但是数组名是首元素的地址,这个数组是二维数组,所以它的数组名是一维数组,用int* arr接收错误
  • 第五个test错误,虽然传入的是数组,但是数组名是首元素的地址,这个数组是二维数组,所以它的数组名是一维数组,用int* arr[5]接收错误,这个东西是指针数组,即不是用正确的数组接收,也不是用正确的指针接收,
  • 第六个test正确,虽然传入的是数组,但是数组名是首元素的地址,这个数组是二维数组,所以它的数组名是一维数组,用int (*arr)[5]接收,这个东西是数组指针没有问题
  • 第七个test错误,形参是int **arr,只有传过去是一级整形指针,或int *的地址,才能用int **arr接收,显然这里什么也不是

一级指针传参

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void print(int* p, int sz) 
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d\n", *(p + i));
	}
}
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;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

比如:

void test1 ( int * p ) {} //test1 函数能接收什么参数? void test2 ( char* p ) {} //test2 函数能接收什么参数?
  •  对于test1,int a = 0;可以传入a的地址,即test1(&a),int *pr = &a,可以传入pr指针,即test(pr),test2也同理,

二级指针传参

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?

 征服C语言之指针的进阶

 五.函数指针

什么是函数指针?就是一个指针,它指向函数,

函数指针 - 存放函数地址的指针

首先看一段代码: 征服C语言之指针的进阶

 从上面的代码中我们可以得到一个很重要的结论,函数名==&函数名,顺便一提数组名!=&数组名,


看代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//int (*pf)(int, int) = &Add;//OK
	int (*pf)(int, int) = Add;//Add === pf
	int ret = 0;

	ret = (*pf)(3, 5);//1
	printf("%d\n", ret);

    ret = pf(3, 5);//2
	printf("%d\n", ret);

	ret = Add(3, 5);//3
	printf("%d\n", ret);
	//int ret = * pf(3, 5);//err
	return 0;
}

征服C语言之指针的进阶

  • pf就是一个函数指针变量 
  • 这串代码,也从侧面证明出了对于一个函数指针,使用它的*操作符看上去没用
阅读两段有趣的代码:
// 代码 1 ( * ( void ( * )()) 0 )(); // 代码 2 void ( * signal ( int , void ( * )( int )))( int );
  •  调用0地址处的函数,该函数无参,返回类型是void
  • 1. void(*)() - 函数指针类型
  • 2. (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
  • 3. *(void(*)())0 - 对0地址进行了解引用操作
  • 4. (*(void(*)())0)() - 调用0地址处的函数

征服C语言之指针的进阶  

  • signal 和()先结合,说明signal是函数名
  • signal函数的第一个参数的类型是int,第二个参数的类型是函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
  • signal函数的返回类型也是一个函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
  • signal是一个函数的声明,

这里可以用

typedef void(*pfun_t)(int) ;//对void(*)(int)的函数指针类型重命名为pfun_t

代码二的简化:pfun_t signal(int, pfun_t);

六.函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:
int * arr [ 10 ]; // 数组的每个元素是 int*

 什么是函数指针数组?简单的说就是,一个数组,它的元素就是函数指针

比如:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
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就是函数指针数组
	int ret = pfArr[0](2, 4);//6
	printf("%d",ret);
	return 0;
}
  • pfArr就是函数指针数组,数组的元素是函数的地址,用函数指针指向
  • pfArr[0]就是找到下标为0的元素,函数指针的解引用*操作符可以不写
  • int (*pfArr[2])(int, int) = {Add, Sub};叫做初始化
  • pfArr[0](2, 4);叫做调用

实现一个简单的计算器

加法,减法,乘法,除法

一般写法

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}

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 a>b

	do {
		menu();
		
		int x = 0;
		int y = 0;
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("请输入2个操作数>:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数>:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数>:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数>:");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}
		
	} while (input);
	return 0;
}

征服C语言之指针的进阶

  • 这样的写法是不是感觉不够美观,代码写的很冗余

函数指针数组的写法

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}

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 a>b

	do {
		menu();
		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);
			//ret = pfArr[input](x, y);OK
			printf("ret = %d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出程序\n");
			break;
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);//只有输入0才退出
	return 0;
}
  • 这样的写法代码看上去会不会简明一点呢?
  • 函数指针数组的用途: 转移表 ,也有一点点像跳板,
  • 其中加减乘除的函数返回值的类型需要一样,

七.指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针; 如何定义?
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void test(const char* str) 
{
	printf("%s\n", str);
}
int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
	return 0;
}

八.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
  •  说人话就是一个A函数的地址传给B函数,则B函数就是用函数指针接收,同理C,D,E,F函数的地址也可以传给B函数,
  • B函数,这一个函数就可以解引用,调用多个函数,

回调函数实现计算器

#define _CRT_SECURE_NO_WARNINGS 1
#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;
}

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 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;
}

征服C语言之指针的进阶

  • Clac这一个函数就能调用多个函数,减少了代码的冗余,Clac就像一个集成器,

首先演示一下qsort函数的使用: 

征服C语言之指针的进阶 征服C语言之指针的进阶

  •  不难发现通过,qsort函数之后,使得arr变成了升序,
  • 其中qsort不知道使用者的比较方法是什么,所以这里的使用方法是需要自己设计,
  • 还有qsort不知道使用者排序的类型是什么,所以比较方法中的形参需要设计成void*
  • void*的指针能接受任意类型的指针,

 模拟实现qsort函数

征服C语言之指针的进阶

其中这段代码有两个灵魂设计点

第一个就是:传入函数参数用void*,然后又通过char*进行一个字节一个字节的访问,比如

征服C语言之指针的进阶

征服C语言之指针的进阶征服C语言之指针的进阶

 第二个就是使用了回调函数

征服C语言之指针的进阶

征服C语言之指针的进阶

九.指针和数组笔试题解析

一维数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//16
	printf("%d\n", sizeof(a + 0));//4/8
	printf("%d\n", sizeof(*a));//4
	printf("%d\n", sizeof(a + 1));//4/8
	printf("%d\n", sizeof(a[1]));//4

	printf("%d\n", sizeof(&a));//4/8
	printf("%d\n", sizeof(*&a));//16
	printf("%d\n", sizeof(&a + 1));//4/8
	printf("%d\n", sizeof(&a[0]));//4/8
	printf("%d\n", sizeof(&a[0] + 1));//4/8
	return 0;
}
  1.  sizeof(a)中的a是数组名,但是这里有sizeof,所以计算的是整个数组的大小,则结果为16
  2. sizeof(a+0)中的a+0表示这个数组第一个元素的地址,则结果为4/8,
    在32位平台下地址的大小为4,在64位平台下地址的大小为8,
    因为在32位机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,64位机器也同理,
  3. sizeof(*a)中的*a为对数组名进行解引用,则找到数组的第一个元素,则结果为4
  4. sizeof(a+1)中的a为首元素的地址,a + 1是第二个元素的地址,则结果为4/8,
  5. sizeof(a[1])中的a[1]是数组的第2个元素,因为数组下标是从0开始的,则结果为4,
  6. sizeof(&a)中的&a表示取数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
  7. sizeof(*&a)中的&a表示取数组的地址,再解引用,则*&a表示这整个数组,则结果为16,
  8. sizeof(&a+1)中的&a表示取整个数组的地址,&a + 1 表示跳过一个数组,指向的是数组后面的空间的地址,结果为4/8
    征服C语言之指针的进阶
  9. sizeof(&a[0])中的a[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,则结果为4/8
  10. sizeof(&a[0]+1)中的a[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,结果是4/8,

字符数组

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6
	printf("%d\n", sizeof(arr + 0));//4/8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4/8
	printf("%d\n", sizeof(&arr + 1));//4/8
	printf("%d\n", sizeof(&arr[0] + 1));//4/8
	return 0;
}
  1. sizeof(arr)中的arr是数组名,但是这里有sizeof,所以计算的是整个数组的大小,则结果为16
  2. sizeof(arr+0)中的arr+0表示这个数组第一个元素的地址,则结果为4/8,
  3. sizeof(*arr)中的*arr为对数组名进行解引用,则找到数组的第一个元素,则结果为1
  4. sizeof(arr[1])中的arr[1]是数组的第2个元素,因为数组下标是从0开始的,则结果为1
  5. sizeof(&arr)中的&arr表示取数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
  6. sizeof(&arr+1)中的&arr表示取整个数组的地址,&a + 1 表示跳过一个数组,指向的是数组后面的空间的地址,结果为4/8
    征服C语言之指针的进阶
  7. sizeof(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,则结果是4/8,

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	printf("%d\n", strlen(*arr));//err
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值-6
	printf("%d\n", strlen(&arr[0] + 1));//随机值-1
	return 0;
}
  1.  strlen(arr)中的arr是数组名,表示数组首元素的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
  2. strlen(arr+0)中的arr是数组名,表示数组首元素的地址,arr+0还是数组首元素的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
  3. strlen(*arr)中的*arr表示这个数组的第一个元素a,a字符的ASCII码值是97,相当于传了个97给strlen,strlen认为97是个地址,然后访问,会出错,则结果为err,
    strlen在库函数中为size_t strlen ( const char * str );它只能接收一个字符型的地址,访问从首元素地址到/0结束,然后返回字符串的长度,字符串的长度等于字符串开头和结束的空字符之间的字符数(不包括结束的空字符即/0本身)
  4. strlen(arr[1])中的arr[1]表示这个数组的第二个元素b,b字符的ASCII码值是98,相当于传了个98给strlen,strlen认为98是个地址,然后访问,会出错,则结果为err,
  5. strlen(&arr)中的&arr表示数组的地址,strlen是从数组首元素的地址开始读取,但是这个arr数组中没有\0所以导致停不下来,则结果为随机值
  6. strlen(&arr+1)中的&arr表示数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,但是还是不知道\0在哪里,但这个访问的个数又比最上面那个的个数少六,则结果为随机值-6,
    征服C语言之指针的进阶
  7. strlen(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&a[0]取出首元素的地址,&a[0]+1表示数组的第二个元素的地址,strlen从数组的第2个元素的地址开始访问,但是还是不知道\0在哪里,但这个访问的个数又比最上面那个的个数少一,则结果为随机值-1,征服C语言之指针的进阶

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));//7
	printf("%d\n", sizeof(arr + 0));//4/8
	printf("%d\n", sizeof(*arr));//1
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4/8
	printf("%d\n", sizeof(&arr + 1));//4/8 
	printf("%d\n", sizeof(&arr[0] + 1));//4/8
	return 0;
}
  1. sizeof(arr)中计算的是这个数组的大小,这个"abcdef"中自动补了个\0,且隐藏了,实际上arr数组内容为"abcdef\0" ,则结果为7
  2. sizeof(arr+0)中 arr + 0 表示这个数组第一个元素的地址,则结果为4/8
  3. sizeof(*arr)中的*arr为对数组名进行解引用,则找到数组的第一个元素,则结果为1
  4. sizeof(arr[1])中的arr[1]表示数组的第2个元素,因为数组下标是从0开始的,则结果为1
  5. sizeof(&arr)中&arr表示取整个数组的地址,虽然是数组的地址,但是也是地址,则结果为4/8
  6. sizeof(&arr+1)中&arr表示取整个数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,结果为4/8
    征服C语言之指针的进阶
  7. sizeof(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&arr[0]取出首元素的地址,&arr[0]+1表示数组的第二个元素的地址,sizeof(&arr[0]+1)计算的是数组第2个元素的地址大小,结果是4/8,

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	printf("%d\n", strlen(*arr));//err
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	return 0;
}
  1.   strlen(arr)中的arr是数组名,表示数组首元素的地址,strlen是从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
  2. strlen(arr+0)中的arr是数组名,表示数组首元素的地址,arr+0还是表示数组首元素的地址strlen从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
  3. strlen(*arr)中的*arr表示这个数组的第一个元素a,a字符的ASCII码值是97,相当于传了个97给strlen,strlen认为97是个地址然后访问,则会出错,结果为err,
  4. strlen(arr[1])中的arr[1]表示这个数组的第二个元素b,b字符的ASCII码值是98,相当于传了个98给strlen,strlen认为98是个地址然后访问,则会出错,结果为err,
  5. strlen(&arr)中的&arr表示数组的地址,strlen是从数组首元素的地址开始读取,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为6
  6. strlen(&arr+1)中的&arr取出的是数组的地址,&arr + 1 表示跳过一个数组,指向的是数组后面空间的地址,但是不知道\0在哪里,所以所以结果为随机值,
    征服C语言之指针的进阶
  7. strlen(&arr[0]+1)中的arr[0]表示的是数组的第一个元素,&arr[0]取出首元素的地址,&arr[0]+1表示数组的第二个元素的地址,strlen从数组的第2个元素的地址开始访问,这个arr数组中有\0,strlen读取到\0结束,然后返回字符串的长度,则结果为5征服C语言之指针的进阶

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	char* p = "abcdef";
	printf("%d\n", sizeof(p));//4/8
	printf("%d\n", sizeof(p + 1));//4/8
	printf("%d\n", sizeof(*p));//1
	printf("%d\n", sizeof(p[0]));//1
	printf("%d\n", sizeof(&p));//4/8
	printf("%d\n", sizeof(&p + 1));//4/8
	printf("%d\n", sizeof(&p[0] + 1));//4/8
	return 0;
}
  1.  sizeof(p)中的p是一个字符型指针变量,且指向a的地址,则结果是4/8
    征服C语言之指针的进阶
  2. sizeof(p+1)中的p+1指向b的地址,则结果是4/8
    征服C语言之指针的进阶
  3. sizeof(*p)中的*p表示对p进行解引用,指向a的地址,然后访问,则结果是1
    征服C语言之指针的进阶
  4. sizeof(p[0])中的p[0]等价于*(p+0),表示指向a的地址,然后访问,则结果是1
    征服C语言之指针的进阶
  5. sizeof(&p)中的&p表示取p的地址,结果为4/8
    征服C语言之指针的进阶
  6. sizeof(&p+1)中的&p表示取p的地址,&p+1则跳过p的地址,则结果为4/8
    征服C语言之指针的进阶
  7. sizeof(&p[0]+1)中的&p[0]+1表示b的地址,则结果还是4/8
    征服C语言之指针的进阶

二维数组 

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48 
	printf("%d\n", sizeof(a[0][0]));//4 
	printf("%d\n", sizeof(a[0]));//16
	printf("%d\n", sizeof(a[0] + 1));//4/8
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	printf("%d\n", sizeof(a + 1));//4/8
	printf("%d\n", sizeof(*(a + 1)));//16
	printf("%d\n", sizeof(&a[0] + 1));//4/8
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	printf("%d\n", sizeof(*a));//16
	printf("%d\n", sizeof(a[3]));//16
	return 0;
}
  1.  sizeof(a)中的a是数组首元素地址,但是sizeof(数组名)计算的是数组的大小,则结果是48
  2. sizeof(a[0][0])中的a[0][0]表示这个数组的第一行第一列的元素,则结果是4
  3. sizeof(a[0])中的a[0]表示这个数组的第一行,a[0]相当于第一行的数组名,sizeof(数组名)计算的是数组的大小,则结果为16
    二维数组在内存中是连续存放的
    征服C语言之指针的进阶
    实际上在内存中是这样的,见下表:
    征服C语言之指针的进阶
  4. sizeof(a[0]+1)中的a[0]表示这个数组的第一行,a[0]相当于第一行的数组名,a[0]作为数组名并没有单独放在sizeof内部,也没取地址,所以a[0]就是第一行第一个元素的地址,a[0]+1就是第一行第二个元素的地址,则结果为4/8
  5. sizeof(*(a[0]+1)中的a[0]+1就是第一行第二个元素的地址,*(a[0]+1)表示第一行第二个元素,则结果为4
  6. sizeof(a+1)中的a是二维数组的数组名,并没有取地址,也没有单独放在sizeof内部,所以a就表示二维数组首元素的地址,所以表示第一行的地址,a + 1就是二维数组第二行的地址,则结果为4/8
  7. sizeof(*(a + 1))中的a + 1是二维数组第二行的地址,*(a + 1)表示二维数组第二行的数组名sizeof(数组名)计算的是二维数组第二行的大小,则结果为4
  8. sizeof(&a[0] + 1)中a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1 就是第二行的地址,则结果为4/8
  9. sizeof(*(&a[0] + 1))中&a[0]+1就是第二行的地址,*(&a[0]+1) 就是第二行,所以计算就是第二行的大小,则结果为16
  10. sizeof(*a)中的a作为二维数组的数组名,没有&,没有单独放在sizeof内部,a就是首元素的地址,即第一行的地址,所以*a就是第一行,计算的是第一行的大小,结果为16
  11. sizeof(a[3])中的a[3]其实是第四行的数组名(如果有的话),a[3]的类型属性-int [4],其中sizeof()内部的表达式不计算,所以其实不存在,也能通过类型计算大小的,结果为16
    表达式有两个属性,一个是值属性,另一个是类型属性,
    如3+5,值属性-8,类型属性-int,则sizeof(3+5)等于4

总结: 数组名的意义如下

1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。

 十.指针笔试题

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));//2,5
	return 0;
}

  &a表示取出这个数组的地址,&a+1跳过一个数组,指向的是数组后面空间的地址,它的原类型为Int(*)[5],再将其强制类型转换为int*,再将其赋给指针变量ptr
征服C语言之指针的进阶
*(ptr - 1)解释:ptr向后跳过一个int,然后解引用找到并且访问5
*(a+1)解释:a为数组首元素,a+1为数组的第二个元素,然后解引用找到并且访问2
征服C语言之指针的进阶


#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	struct Test
	{
		int Num;
		char* pcName;
		short sDate;
		char cha[2];
		short sBa[4];
	}*p;
	//假设p 的值为0x100000。如下表表达式的值为多少
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

考查的是:指针类型决定了指针的运算 

  1.  p+0x1中p为结构体指针变量,这个结构体的大小为20,0x1实际上就是1,p+1会跳过一个结构体的大小,指向的是数组后面空间的地址0x100000+20=0x100014,结果为0x100014
  2. (unsigned long)p + 0x1中将p强制类型转换为unsigned long,它加1就是加1,0x100000+1=0x100001
  3. (unsigned int*)p+0x1中将p强制类型转换为unsigned long*,p变成了无符号整形指针,它加一就是加一个int,0x100000+4=0x100004

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);//4,2000000
	return 0;
}
  1.  ptr1是一个整形指针,指向的是数组后面空间的地址,&a取出的是数组的地址
    征服C语言之指针的进阶
  2. ptr2是一个整形指针,(int)a + 1中a表示首元素的地址,再将其强制类型转换问int,它加一就是加一(地址加1),相当于向后偏移了一个字节,在内存中一个字节给一个地址,如:0x0012ff44-->int+1-->0x0012ff45
    在小端机器下,
    征服C语言之指针的进阶
  3. *ptr2表示对ptr2进行解引用,找到并访问4个字节,ptr1[-1]等价于*(ptr1-1),结果为4,2000000
    征服C语言之指针的进阶

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);//1
	return 0;
}

  (0, 1), (2, 3), (4, 5)这几个为逗号表达式,等价于1,3,5,
a[0]表示这个二维数组的首元素的地址,p为整形指针变量,p[0]等价于*(p+0),结果为1
征服C语言之指针的进阶


#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	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]);//FFFFFFFC,-4
	return 0;
}

 p=a中a的类型为int(*)[5],p的类型为int (*)[4],p[4][2]等价于*(*(p+4)+2),&p[4][2] - &a[4][2]计算的是两者之间的元素个数,随着数组下标的增长,地址是由低地址到高地址变化的,所以结果为-4,
征服C语言之指针的进阶
-4以%d的形式打印还是-4
-4的原码10000000000000000000000000000100
-4的反码111111111111111111111111111111111011
-4的补码111111111111111111111111111111111100
-4在内存中以补码的形式存储,%p的形式打印,会直接将-4的补码当作原码打印出来所以结果为FFFFFFFC


#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	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));//10,5
	return 0;
}
  1.   int* ptr1 = (int*)(&aa + 1)中&aa是取出这个数组的地址,&aa+1则跳过一个数组,再将其强制类型转换为int*,再将其赋给指针变量ptr,ptr1是一个整形指针,指向的是数组后面空间的地址,*(ptr1 - 1)的结果是10
    征服C语言之指针的进阶
  2.  int* ptr2 = (int*)(*(aa + 1))中aa是二维数组的数组名,是第一行的地址,aa+1则跳过第一行,指向第二行,再将其强制类型转换为int*,再将其赋给指针变量ptr2,ptr2是一个整形指针,ptr2为第二行首元素的地址,*(ptr2 - 1)的结果是5
    征服C语言之指针的进阶

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);//at
	return 0;
}

  char* a[]是一个字符型指针数组,a为数组名,是数组首元素的地址,
char** pa = a中a是一个一级指针的地址,所以用二级指针接收刚好,pa是一个二级指针变量,指向的是数组首元素的地址,结果是at
征服C语言之指针的进阶


#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);//POINT
	printf("%s\n", *-- * ++cpp + 3);//ER
	printf("%s\n", *cpp[-2] + 3);//ST
	printf("%s\n", cpp[-1][-1] + 1);//EW
	return 0;
}

 

  1.   char*c[],char**cp[],char***cpp这三者之间的指向关系如下:
    征服C语言之指针的进阶
  2. **++cpp表示先cpp+1,再解引用,指向c+2的地址,再解引用,指向P的地址,结果为POINT
    征服C语言之指针的进阶
  3. *-- * ++cpp + 3表示先cpp+1,由于上面的运算cpp变成了cpp+1,所以这里的cpp变成了cpp+2,再解引用,指向c+1的地址,再减1,指向c的地址,再解引用,指向E的地址,再加3,指向第四个E的地址,结果为ER
    征服C语言之指针的进阶
  4. *cpp[-2] + 3等价于*(*(cpp-2))+3表示为cpp-2,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp,再解引用,指向c+3地址,再解引用,指向F的地址,再加3,指向S的地址,结果为ST
    征服C语言之指针的进阶
  5. cpp[-1][-1] + 1等价于*(*(cpp-1)-1)+1,由于上面的运算cpp变成了cpp+2,所以这里的cpp变成了cpp+1,再解引用,指向c+2的地址,再减1,指向c+1的地址,再解引用,指向N的地址,再加一指向E的地址,结果为EW
    征服C语言之指针的进阶

//阅读两段有趣的代码
//代码1
(*(void(*)())0)();//出自《C陷阱和缺陷》
//代码2
void(*signal(int , void(*) (int)))(int);//出自《C陷阱和缺陷》

  1. 解析代码1:突破口在0那里,调用0地址处的函数,该函数无参,返回类型是void
    (1)void(*)() - 函数指针类型
    (2) (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
    (3)*(void(*)())0 - 对0地址进行解引用操作
    (4)(*(void(*)())0)() - 调用0地址处的函数
  2. 解析代码2:突破口在signal那里,是一个函数是声明
    (1)signal先和()结合,说明signal是函数名,
    (2)signal函数的第一个参数的类型是int,第二个参数的类型是,该函数指针指向一个参数为int,返回类型为void的函数
    (3)signal函数的返回类型也是一个函数指针,该函数指针指向一个参数为int,返回类型为void的函数

简化代码2:

  • void(*)(int) signal (int,void(*)(int)) 这样写更好理解,但是在语法上第一个的*必须跟signal结合在一起,
  • 这里我们可以使用typedef,它可以对类型进行重定义,typedef  void(*)(int) pfun_t
  • 但是在语法上第一个的*必须跟pfun_t结合在一起,所以正确写法为typedef  void(*pfun_t)(int)
  • 最后简化后的代码为pfun_t signal (int , pfun_t)

--------------------------------------------------------------------------------------------------------------------------------本章完 

上一篇:P9 鸡兔同笼


下一篇:数组知识点