指针的进阶

本文深入探讨了C/C++中的指针和数组,包括字符指针、指针数组、数组指针、数组参数、函数指针、回调函数以及相关面试题的解析。通过实例展示了各种指针类型和数组操作,强调了指针在数组和函数调用中的作用,以及在内存管理和程序设计中的重要性。
摘要由CSDN通过智能技术生成


指针的概念:

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

1. 字符指针

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

一般使用:

int main()
{
    char ch = 'w';
    char *pc = &ch; // pc 是指向一个字符的变量
    *pc = 'w';
    return 0;
}

还要一种使用方式如下:

int main()
{
    char arr[] = "abcdef";
    char* pc = arr;
    printf("%s\n", arr);
    printf("%s\n", pc);
    return 0;
}


int main()
{
    // char* p = "abcdef"; // "abcdef" 是一个常量字符串
    const char* p = "abcdef"; // 最准确的写法
    printf("%s\n", p); // abcdef
    printf("%c\n", *p); // a

    // *p = 'W'; // 报错  -- "abcdef" 是 常量字符串
    printf("%s\n", p);
    return 0;
}

代码char* p = "abcdef";,本质是把字符串abcdef首字符的地址放到了p中。

56

面试题 (来源于《剑指offer》):

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	char* p1 = "abcdef";
    // const 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;
}

最终运行结果:

57

这里 p1 和 p2 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存,但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 arr1 和 arr2 不同,p1 和 p2 相同。

2. 指针数组

指针数组 是一个存放指针的数组。

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

// 写法一
int main()
{
	int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
    
    int* arr[4] = {&a, &b, &c, &d}; // arr2 就是整型指针的数组
    int i = 0;
    for(i = 0;i<4;i++)
    {
        printf("%d ",*arr[i]);
    }
    return 0;
}


// 写法二
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(parr[i] + j));
            // *(parr[i]+j) == parr[i][j]
		}
		printf("\n");
	}
	return 0;
}


// 写法三
int main()
{
    const char* arr[5] = {"abcdef","bcdefg","hehe","haha","heihei"};
    int i = 0;
    for(i = 0; i<5; i++)
    {
        printf("%s\n", arr[i]);
    }
    return 0;
}

3. 数组指针

3.1 数组指针的定义

数组指针是 能够指向数组的指针

int main()
{
    int a = 10;
    int* pa = &a;// 整型的地址存放在整型指针中
	
    char ch = 'w';
    char*pc = &ch; // 字符的地址存放在字符指针中
	
    
    // 数组指针 -- 指向数组的指针  -- 存放数组的地址

	// int arr[10] = { 0 };
	// arr -- 数组名是首元素的地址
	// &arr[0] -- 数组首元素的地址
	// &arr  -- 数组的地址

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr; // 数组首元素的地址 
	int(*parr)[10] = &arr; // 取出的是数组的地址,应该存放到数组指针中
    // 数组指针的类型 -- int(*)[10]
 	return 0;
}

下面代码哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

解释:

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

59

3.2 &数组名 VS 数组名

58

根据代码看出,arr是数组名,虽然 arr&arr 的值是一样的,但是 &arr表示的是 数组的地址

arr的类型是int*&arr的类型是int(*)[10],指针的类型决定了指针加减 1 跳过的长度。

3.3 数组指针的使用

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

看代码:

自讨苦吃的写法:

#include <stdio.h>

// print1 和 print2 较为简单
void print1(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void print2(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
}

// 数组指针

//void print3(int(*parr)[10], int sz) // 这是一个错误示范
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", parr[i]); // parr[i] == *(parr+i)
//	}
//}

// 自讨苦吃的写法
void print4(int(*parr)[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(*parr + i)); // (*parr) 相当于 parr 指向的数组的数组名
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	// print1(arr, sz);
	// print2(arr, sz);
	// print3(&arr, sz);
	print4(&arr, sz);

	return 0;
}

一个数组指针的正确使用:

// 参数是数组的形式
void print1(int arr[3][5], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

// 参数是指针的形式
void print2(int(*p)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			// printf("%d ", *(*(p+i)+j));
			// printf("%d ", (*(p + i))[j]);
            // printf("%d ", *(p[i] + j));
            printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	//int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int i = 0;
	//int* p = arr;
	//for (i = 0; i < 10; i++)
	//{
	//	printf("%d ", p[i]);
	//	printf("%d ", *(p + i));
	//	printf("%d ", *(arr + i));
	//	printf("%d ", arr[i]); 
	//	// arr[i] == *(arr+i) == *(p+i) == p[i]
	//}
    
	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,表示首元素的地址
    // 但是二维数组的首元素是二维数组的第一行
    // 所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    // 可以数组指针来接收
    
	return 0;
}

下面代码的意思:

int arr[5];       // arr 是一个5个元素的整型数组。
int* parr1[10];   // parr1 是一个数组,数组有十个元素,每个元素的类型是int*,parr1 是一个指针数组。
int (*parr2)[10]; // parr2 是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型是 int ,parr2是数组指针。
int (*parr3[10])[5]; // parr3 是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int
// 去掉变量名之后剩余的就是类型
// int [5]
// int* [10]
// int (*)[10]

image-20210905103611073

4. 数组参数、指针参数

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

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
// void test(int arr[100]) // 虽然语法正确,但是不建议
// {}
void test(int *arr)
{}

void test2(int *arr[20])
{}
void test2(int *arr[])
{}
void test2(int **arr)
{}
int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

// 把一维数组进行传参的时候,参数可以写成 
	// 1. 数组,数组大小可以省略;
	// 2. 数组,数组大小也可以不省略;
	// 3. 指针

4.2 二维数组传参

void test(int arr[3][5])
{}
// void test(int arr[][]) // 错误 -- 行可以省略,列不能省略
// {}
void test(int arr[][5])
{}

//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
// 二维数组的首元素是第一行的地址


void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}


// 下面四种指针只有 int (*arr)[5] 正确

4.3 一级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
     int i = 0;
     for(i=0; i<sz; i++)
     {
     	printf("%d ", *(p+i));
     }
}
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);
     return 0;
}

思考:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test1(int* p)
{}
int main()
{
	int a = 10;
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &a;
	test1(&a); // ok
	test1(p);  // ok
	test1(arr);// ok
}

4.4 二级指针传参

void test(int** ptr)
{
	//printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
    
	int* arr[10];
	test(arr);
    
	return 0;
}

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

void test(char **p)
{

}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);
 return 0;
}

5. 函数指针

数组指针 ——> 是指向数组的指针。

函数指针 ——> 是指向函数的指针 – 存放函数地址的一个指针。

62

存放指针的地址:

int Add(int x, int y)
{
	return x + y;
}
int main()
{
    // 数组指针
    int arr[10] = {0};
    int (*parr)[10] = &arr; // parr 就是数组指针变量
    // 数组指针类型 -- int(*)[10]   
    
	int (*pa)(int, int) = Add;
    // int (*pa)(int x, int y) = Add;
    // int (*pa)(int, int) = &Add;
	// 函数指针类型 -- int(*)(int,int)
    
	printf("%d\n", (*pa)(2, 3)); // 5
	return 0;
}

// pa 先与 * 结合,说明 pa 是指针,指针指向一个函数,函数的参数为两个 int ,返回值为 int 。
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pa)(int, int) = Add;

    // 这里的 * 没用任何意义,不写或者写多个效果一样
	printf("%d\n", (*pa)(2, 3)); // 5

	printf("%d\n", (pa)(2, 3));  // 5
	printf("%d\n", pa(2, 3)); // 5
	printf("%d\n", Add(2, 3)); // 5


	//printf("%d\n", (**pa)(2, 3)); // 5
	//printf("%d\n", (***pa)(2, 3)); // 5
	return 0;
}
void Print(char* str)
{
	printf("%s\n", str);
}
int main()
{
	void (*p)(char*) = Print;
    // void (*)(char*)  -- 去掉变量名之后剩下的就是  类型
	(*p)("hello bit");
	return 0;
}

阅读两段有趣的代码:

:推荐《C陷阱和缺陷》 – 这本书中提及这两个代码。

//代码1
(*(void (*)())0) ();

// 解析:
// 1. 代码中把0强制类型转换为类型为 void(*)()的一个函数的地址。
// 2. 解引用0地址,就是取0地址处的该函数,被调用的函数是无参,返回类型是void


// 代码2
void (*signal(int , void(*)(int)))(int);

// 解析:
// 这个代码是一次函数声明
// 声明的函数名是 signal
// signal 函数的参数有2个,第一个是int类型,第二个是 void(*)(int) 的函数指针类型
// signal 函数的返回类型也是 void(*)(int) 的函数指针类型

将代码2进行简化:

void(*signal(int, void(*)(int)))(int);
// void(*)(int) signal(int, void(*)(int)); // error

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

6. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:

int* arr[10]; // 整形指针数组
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr1[10])();
// int *parr2[10]();    // 错误写法
// int (*)() parr3[10]; // 错误写法
// parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

练习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;
}
int main()
{
	// 指针数组
	int* arr[5];
	// 需要一个数组,这个数组可以存放4个函数的地址 -- 函数指针的数组
	int (*pa)(int, int) = Add; // Sub / Mul / Div
	int (*parr[4])(int, int) = { Add,Sub,Mul,Div }; // 函数指针的数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n",parr[i](2, 3)); // 5 -1 6 0
	}
	return 0;
}

练习2

char* my_strcpy(char* dest, const char* src);
// 写一个函数指针pf,能够指向my_strcpy
char* (*pf)(char*, const char*);

// 写一个函数指针数组 PfArr,能够存放 4 个my_strcpy函数的地址。
char* (*pfArr[4])(char*, const char*);

函数指针数组的用途:转移表。-- 《C和指针》

例子:(计算器)

void menu()
{
	printf("**********************************\n");
	printf("*********  1.add   2.sub  ********\n"); 
	printf("*********  3.mul   4.div  ********\n"); 
	printf("*********      0.exit     ********\n");
	printf("**********************************\n");
}

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 input = 0;
	int x = 0;

	int y = 0;
	do
	{

		menu();
		printf("请选择:>");
		scanf("%d", &input);

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

改进代码:

// 增加函数 Calc()
void Calc(int (*pf)(int,int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:>");
	scanf("%d%d", &x, &y);
	printf("%d\n", pf(x, y));
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			Calc(Add);
                // Add 函数被称为回调函数
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

使用函数指针数组的实现:

void menu()
{
	printf("**********************************\n");
	printf("*********  1.add   2.sub  ********\n"); 
	printf("*********  3.mul   4.div  ********\n"); 
	printf("*********  5.xor   0.exit ********\n");
	printf("**********************************\n");
}

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 input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div }; // 转移表
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出\n");
			break;
		}
		else
		{
			printf("选择错误\n");
		}

	} while (input);
	return 0;
}

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

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

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr; // 取出数组的地址
	// p是一个指向 整型数组 的指针
    
    int* arr[10]; // 整型指针的数组
    int* (*p)[10] = &arr; // 整型指针数组的地址
    // p是一个指向 (整型指针数组) 的指针
    
	int (*pf)(int, int); // pf 是函数指针
	int (*pfArr[4])(int, int); // pfArr 是一个数组 - 函数指针的数组
	int (*(*ppfArr)[4])(int, int) = &pfArr;
    // ppfArr 是一个指向 【函数指针数组】 的指针
	// ppfArr 是一个数组指针,指针指向的数组有4个元素
	// 指向的数组的每个元素的类型是一个函数指针   int(*)(int, int)
	return 0;
}

8. 回调函数

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

void*指针的用法:

int main()
{
	int a = 10;
	//int* pa = &a; 
	// char* pc = &a;  // --- 会报警告

	//char ch = 'w';
	void* p = &a; //  -- 不会报警告
	
	// *p = 0;  //  -- 报错
	// p++;     //  -- 报错
    
	//p = &ch;
    
	// void* 类型的指针 可以接受任意类型的地址
	// void* 类型的指针 不能进行解引用操作、也不能进行 +- 整数的操作
	return 0;
}

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

// void qsort( void* base, 
//			   size_t num,    // 待排序的元素个数
//			   size_t width,  // 一个元素的大小,单位是字节
//			   int (* cmp )(const void *e1, const void *e2 )  // cmp 指向的是:排序时,用来比较2个元素的函数
//			   );

//qsort函数的使用者得实现一个比较函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	// 趟数
	for (i = 0; i < sz - 1; i++)
	{
		// 一趟冒泡排序
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

struct Stu
{
	char name[20];
	int age;
};

int cmp_int(const void* e1, const void* e2)
{
	// 比较两个整型值的
	return (*(int*)e1 - *(int*)e2);
}
void test1()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int cmp_float(const void* e1, const void* e2)
{
	// 比较两个整型值的
	if ((*(float*)e1 == *(float*)e2))
		return 0;
	else if ((*(float*)e1 > *(float*)e2))
		return 1;
	else
		return -1;
	//return (int)(*(float*)e1 - *(float*)e2);  -- 存在问题
}
void test2()
{
	//float f[] = { 9.0,8.0,7.0,6.0,5.0,4.0,3.0 };
	float f[] = { 1.1,1.2,1.4,1.3 };
	int sz = sizeof(f) / sizeof(f[0]);
	qsort(f, sz, sizeof(f[0]), cmp_float);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%.3f ", f[i]);
	}
}

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

int cmp_stu_by_name(const void* e1, const void* e2)
{
	// 字符串比较不能直接用 ><= 来比较,应该用strcmp 函数、
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test3()
{
	struct Stu s[3] = { {"zhangsan",20},{"lisi",21},{"wangwu",18} };
	int sz = sizeof(s) / sizeof(s[0]);

	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	// qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);

}
int main()
{
	//test1();
	//test2();
	test3();


	return 0;
}

使用回调函数,模拟实现qsort(采用冒泡的方式)。

struct Stu
{
	char name[20];
	int age;
};
int cmp_int(const void* e1, const void* e2)
{
	// 比较两个整型值的
	return (*(int*)e1 - *(int*)e2);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

int cmp_stu_by_name(const void* e1, const void* e2)
{
	// 字符串比较不能直接用 ><= 来比较,应该用strcmp 函数、
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//void qsort( void *base, 
//			size_t num, 
//			size_t width, 
//			int (*cmp)(const void* e1, const void* e2 ) 
//			);
	// 第一个参数 : 待排序数组的首元素地址
	// 第二个参数 : 待排序数组的元素个数
	// 第三个参数 : 待排序数组的每个元素的大小 -- 单位是字节
	// 第四个参数 : 是函数指针,比较两个元素的所用函数的地址 -- 这个函数使用者自己实现 。
	//				函数指针的两个参数是 : 待比较的两个元素的地址

// 实现 bubble_sort 函数的程序员,他是否直到未来排序的数据类型 -- 不知道
// 那程序员也不知道,待比较的两个元素的类型


void bubble_sort(void* base, size_t sz, size_t width,int (*cmp)(void* e1, void* e2))
{
	size_t i = 0;
	// 趟数
	for (i = 0; i < sz - 1; i++)
	{
		// 每一趟比较的对数
		size_t j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			// 两个元素的比较
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width) > 0)
			{
				// 交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}
void test4()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	// 使用 bubble_sort 的程序员一定指定自己排序的是什么数据
	// 他就应该直到如何比较待排序数组中的元素
	bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);

}

void test5()
{
	struct Stu s[3] = { {"zhangsan",20},{"lisi",21},{"wangwu",18} };
	int sz = sizeof(s) / sizeof(s[0]);
	//bubble_sort(s,sz,sizeof(s[0]),cmp_stu_by_age);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{	
	//test4();
	test5();
	
    // 以前的写法 -- 有局限性  -- 只能比较单一类型的数据
	//bubble_sort(arr, sz);
	//bubble_sort(f, sz);
	return 0;
}

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

一维数组解析:

int main()
{
	// 数组名是首元素的地址
	// 	 但是有两个例外
	// 		1. sizeof(数组名)  -- 数组名表示整个数组,计算的是整个数组的大小,单位是字节。
	// 		2. &数组名         -- 数组名表示整个数组,取出的是整个数组的地址。
	// 除此之外所有的数组名都是首元素地址。
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));      // sizeof(数组名) -- 计算的是数组总大小 -- 单位是字节 -- 16
	printf("%d\n", sizeof(a + 0));  // 数组名这里表示首元素地址,a+0 还是首元素地址,地址的大小就是   -- 4/8
	printf("%d\n", sizeof(*a));     // 数组名表示首元素地址,*a就是首元素,sizeof(*a)就是 --4
	printf("%d\n", sizeof(a + 1));  // 数组名这里表示首元素地址,a+1 是第二个元素的地址,地址的大小就是   -- 4/8
	printf("%d\n", sizeof(a[1]));   // 第二个元素大小 -- 4
	printf("%d\n", sizeof(&a));     // &a取出的是数组的地址,但是数组的地址也是的地址,地址的大小就是 --4/8
	printf("%d\n", sizeof(*&a));    // &a是数组的地址,数组的地址解引用访问的是数组,sizeof计算的是数组的大小  -- 16
	printf("%d\n", sizeof(&a + 1)); // &a是数组的地址,&a+1 虽然地址跳过整个数组,但还是个地址 -- 4/8
	printf("%d\n", sizeof(&a[0]));  // &a[0]第一个元素的地址 -- 4/8
	printf("%d\n", sizeof(&a[0] + 1)); // &a[0+1] 是第二个元素的地址 -- 4/8
	return 0;
}

字符数组解析:

int main()
{
    // strlen - 库函数
    // 求字符串长度
    
    // sizeof - 操作符 - 单位是字符
    // 求变量所占空间的大小
    // 求类型创建的变量所占空间的大小
    
    
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));      // sizeof计算的是数组的大小,6*1 = 6字节
	printf("%d\n", sizeof(arr + 0));  // arr是首元素地址,arr+0还是首元素地址 --4/8
	printf("%d\n", sizeof(*arr));     // arr是首元素地址,*arr就是首元素,首元素是字符大小是 1 个字节
	printf("%d\n", sizeof(arr[1]));   // 1
	printf("%d\n", sizeof(&arr));     // &arr虽然是数组的地址,但还是地址 -- 4/8
	printf("%d\n", sizeof(&arr + 1)); // &arr+1 是跳过整个数组后的地址,地址大小是 -- 4/8
	printf("%d\n", sizeof(&arr[0] + 1));// 第二个元素的地址 -- 4/8

	printf("%d\n", strlen(arr));      // 随机值
	printf("%d\n", strlen(arr + 0));  // 随机值
	printf("%d\n", strlen(*arr));     // 报错 -- strlen 就以为传进来的'a'的ascii码值97就是地址
	printf("%d\n", strlen(arr[1]));   // 报错
	printf("%d\n", strlen(&arr));     // 随机值
	printf("%d\n", strlen(&arr + 1)); // 随机值-6
	printf("%d\n", strlen(&arr[0] + 1));// 随机值-1


	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

	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 &arr  - 数组的地址 -- 数组指针 char(*)[7] = &arr;
	printf("%d\n", strlen(&arr + 1));   // 随机值
	printf("%d\n", strlen(&arr[0] + 1));// 5

	const 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 int arr[10]; arr[0] == *(arr+0)  p[0] == *(p+0) == 'a'
	printf("%d\n", sizeof(&p));         // 4/8
	printf("%d\n", sizeof(&p + 1));     // 4/8  -- 跳过整个字符串
	printf("%d\n", sizeof(&p[0] + 1));  // 4/8

	printf("%d\n", strlen(p));          // 6
	printf("%d\n", strlen(p + 1));      // 5
	printf("%d\n", strlen(*p));         // err
	printf("%d\n", strlen(p[0]));       // err
	printf("%d\n", strlen(&p));         // 随机值  -- &p 存的是 p 的地址,向后读取随机位后停止
	printf("%d\n", strlen(&p + 1));     // 随机值
	printf("%d\n", strlen(&p[0] + 1));  // 5
	return 0;
}

二维数组解析:

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   -- a[0]相当于第一行作为一维数组的数组名,sizeof(arr[0])把数组名单独放在sizeof()内,计算的是第一行的大小
	printf("%d\n", sizeof(a[0] + 1));    // 4/8   -- a[0] 表示的是第一行的数组名,数组名此时是首元素的地址,其实就是第一行第一个元素的地址,所以 a[0]+1 表示的是第一行第二个元素的地址
	printf("%d\n", sizeof(*(a[0] + 1))); // 4
	printf("%d\n", sizeof(a + 1));       // 4/8   -- a 是二维数组的数组名,没用 sizeof(a),也没有&(a),所以 a 是首元素地址,而把二维数组看成一维数组时,二维数组的首元素是他的第一行,a就是第一行(首元素)地址,a+1 就是第二行的地址。
	printf("%d\n", sizeof(*(a + 1)));    // 16    --  等价于sizeof(a[1])的大小
	printf("%d\n", sizeof(&a[0] + 1));   // 4/8   -- 第二行的地址
	printf("%d\n", sizeof(*(&a[0] + 1)));// 16
	printf("%d\n", sizeof(*a));          // 16  -- a 是首元素地址 -- 第一行的地址,*a就是第一行,sizeof(*a)就是计算第一行的大小
	printf("%d\n", sizeof(a[3]));        // 16  -- 等价于 sizeof(a[0]),sizeof() 内部的表达式不参与运算,只是根据他的类型计算大小。
	return 0;
}

image-20210906180225259

指针笔试题

笔试题1:

int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1); // &a数组地址--数组指针类型,强制类型转换成整型指针类型
    printf( "%d,%d", *(a + 1), *(ptr - 1));
    return 0;
}
//程序的结果是什么?

笔试题2:

//由于还没学习结构体,这里告知该结构体Test的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	printf("%p\n", p + 0x1); 
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

解析:

int main()
{
	// p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);   // 00100014
	printf("%p\n", (unsigned long)p + 0x1);   // 00100001
	printf("%p\n", (unsigned int*)p + 0x1);   // 00100004
    
    // 想在前面打印0x
    printf("%#x\n",p + 0x1);// 0x00100014
    
	return 0;
}

笔试题3:

// x86环境下
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);
    return 0;
}

解析:

image-20210908182737378

笔试题4:

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

笔试题5:

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]);
    return 0;
}

解析:

image-20210908190550974

笔试题6:

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));
    return 0;
}

笔试题7:

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

笔试题 8:

int main()
{
	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);
	return 0;
}

image-20210908200431408

希望可以对大家有所帮助,如果有什么不对的地方,还烦请指教,谢谢!

  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值