c语言进阶-第2节-指针

目录

1.字符指针

1.1.字符指针

1.2.练习

2.指针数组

2.1.指针数组

2.2.例子

3.数组指针

3.1.数组指针的定义

3.2.&数组名和数组名

3.3.数组指针的使用

3.4.几个代码表述的意思

4.数组参数,指针参数

4.1.一维数组传参

4.2.二维数组传参

4.3.一级指针传参

4.3.1.一级指针传参例子

4.3.2.函数的参数部分为一级指针时,函数能接收什么参数

4.4.二级指针传参

4.4.1.二级指针传参例子

4.4.2.函数的参数部分为二级指针时,函数能接收什么参数

5.函数指针

5.1.函数指针

5.2.两个有趣的代码

6.函数指针数组(※)

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

8.回调函数(※)

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

10.指针笔试题


1.字符指针

1.1.字符指针

int main()
{
    //1
    char ch='w';
    char* p=&ch;

    //2
    char* p="abcdef";
    *p='w';   //该语句错误

    //3
    const char* p="abcdef";

    return 0;
}

注:

1.代码1是建立变量ch,ch变量空间里面存放字符'w';定义指针变量里面存放变量ch的地址,使指针变量p可以指向变量ch的空间

2.代码2的语句1是将字符串"abcdef"存储在内存的只读数据区(只读数据区里面的数据只能读取),然后将这个字符串的地址(起始地址)放在指针变量p中。

代码2的语句2是不能实现的,因为"abcdef"存在了只读数据区,无法进行更改(强行运行,程序会卡死)

3.代码3是代码2语句1的规范形式。因为代码2不能对只读数据区内的数据进行更改,也就是不能对指针变量p进行解引用,因此在前面用const修饰代码更加严谨。(有些编译器不加const是会报警告的)

1.2.练习

练习一:

#include<stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	const char* str1 = "abcdef";
	const char* str2 = "abcdef";

	if (arr1 == arr2)
		printf("arr1==arr2\n");
	else
		printf("arr1!=arr2\n");

	if (str1 == str2)
		printf("str1==str2\n");
	else
		printf("str1!=str2\n");

	return 0;
}

运行结果:

注:

1.arr1和arr2是两个不同的数组,数组名代表数组首元素的地址,因此arr1不等于arr2

2.只读数据区内,相同的数据只存一份,所以,上面代码的str1和str2均指向只读数据区内字符串"abcdef"的地址(首地址)

练习二:

typedef int* pint;

#define PINT int*

int main()
{

    //1  
    int a,b;

    //2
    int * pa,pb;

    //3
    int * pa, * pb;

    //4
    pint pa,pb;

    //5
    PINT pa,pb;

    return 0;
}

注:

1.代码1中:变量a和b都是int类型

2.代码2中:变量pa是int*类型,是指针变量;变量pb是int类型

3.代码3中:变量pa和pb都是int*类型,是指针变量

4.代码4中:变量pa和pb都是int*类型,是指针变量

5.代码5中:代码运行的时候会将PINT换成int*,因此结果和代码2相同。

   变量pa是int*类型,是指针变量;变量pb是int类型


2.指针数组

2.1.指针数组

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

2.2.例子

例1:

#include<stdio.h>

int main()
{
	char* arr[] = { "abcdef", "qwer", "zhangsan" };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

运行结果:

注:

该数组在内存中是如下图存储的 :

例2:
#include<stdio.h>

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

	int* arr[] = {arr1, arr2, arr3};

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

	return 0;
}

运行结果:

注: 

1.该数组在内存中是如下图存储的 :

2.代码中:arr[ i ][ j ]) 和 *(*(arr+i)+j)是一样的
3.这里可以看出指针数组的变量名相当于一个二级指针

3.数组指针

3.1.数组指针的定义

数组指针是指针
整形指针: int * pint ;        能够指向整形数据的指针。
浮点型指针: float * pf ;    能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针
int * p1 [ 10 ];
int ( * p2 )[ 10 ];
//p1, p2 分别是什么?
解释:
int * p1 [ 10 ];
p1先和[ ]结合,说明p1是一个数组,前面有*,说明是指针数组
int ( * p )[ 10 ];
p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2.&数组名和数组名

代码:

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr+1);

	printf("%p\n", &(arr[0]));
	printf("%p\n", &(arr[0])+1);

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


	return 0;
}

运行结果:

 注:

1.arr和&arr[0]都是数组首元素地址,&arr是数组的地址,他们的值是一样的,但类型不一样

2.整型变量地址,存起来,应该存在整型指针中

   字符型变量地址,存起来,应该存在字符型指针中

   数组的地址,存起来,也应该存在数组指针中,如下:

int (*p)[10]=&arr;

int [10]是数组arr的类型,*表示是一个指针变量,p是变量名。给指针变量p里面放入数组arr的地址。

3.int* arr[6],要把该指针数组的地址保存在数组指针中,数组指针定义应该是int*(*p)[6]=&arr

3.3.数组指针的使用

代码1(1):

代码1(2):

 

代码1的(1)和(2)运行结果相同,如下:

注:

1.对数组指针解引用,相当于得到了这个数组第一个元素的地址,也就是数组名(*p和arr是相同的)。

2.本例题这种用法十分别扭,不要这么使用,这里举出来只有有助于理解。指针数组一般用在二维数组中,如下面代码2所示。

代码2(1):

 代码2(2):

注:

1.二维数组的数组名代表的是数组首元素地址,而二维数组的首元素是二维数组的第一行。也就是说,二维数组数组名是二维数组第一行的地址。上面代码中二维数组第一行类型是int [5],因此形参用int (*p)[5]来接收

2.因为指针变量p指的是第一行,所以给p+i,就是指向二维数组第i行。*p相当于第一行的数组名(数组名是数组首元素地址,也就是第一行第一个元素地址),*(p+i)相当于第i行的数组名(也就是第i行第一个元素的地址)。*(p+i)+j就是第i行第j个元素的地址。*(*(p+i)+j)就是第i行第j个元素。

3.与一维数组类似,这里面*(p+i)相当于p[ i ],*(*(p+i)+j)相当于p[ i ][ j ]

3.4.几个代码表述的意思

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

注:

1.int arr[5];

arr是一个整型数组,有5个元素,每个元素是int类型


2.int *parr1[10];

parr1是一个数组,数组有10个元素,每个元素的类型是int*,所以parr1是指针数组


3.int (*parr2)[10];

parr2和*结合,说明parr2是一个指针,该指针指向一个数组,数组是10个元素,每个元素是int类型的。parr2是数组指针。


4.int (*parr3[10])[5];

parr3和[ ]结合,说明parr3是一个数组,数组是10个元素,数组的每个元素是一种数组指针,其类型是int(*) [5],该类型的指针指向的数组有5个int类型的元素。parr3是元素为数组指针的指针数组。


4.数组参数,指针参数

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

4.1.一维数组传参

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

注:

1.数组传参,形参可以写成数组的形式。(写成数组形式,数组大小可写可不写,写错也没关系,因为在形参这里压根不会创建数组)

2.数组传参,实参传的是数组名,数组名相当于首元素地址,因此形参可以写成指针形式

3.如果实参传的数组中各元素是int类型,那么形参如果用指针接收,应该用int*来接收其地址。

   如果实参传的数组中各元素是int*类型,那么形参如果用指针接收,应该用int**来接收其地址。

4.2.二维数组传参

void test(int arr[3][5])  //ok
{}

void test(int arr[][])  //no
{}

void test(int arr[][5])  //ok
{}

void test(int *arr)  //no
{}

void test(int* arr[5])  //no
{}

void test(int (*arr)[5])  //ok
{}

void test(int **arr)  //no
{}


int main()
{
 int arr[3][5] = {0};
 test(arr);
}

注:

1.数组传参,形参可以写成数组的形式。(写成数组形式,数组大小中行可以省略,列不能省略,因为列如果省略就不是二维数组形式了,一个二维数组,可以不知道有多少行,但是必须知道一行多少元素

2.二维数组传参,实参传的是数组名,二维数组的数组名相当于首元素地址,此处首元素地址是二维数组第一行的地址。

3.上图代码,二维数组第一行的类型是int [5],形参如果用指针接收,应该用int (*) [5]来接收地址。

4.3.一级指针传参

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

4.3.2.函数的参数部分为一级指针时,函数能接收什么参数

void test(int* p)
{}

int main()
{
	int a = 10;
	int* ptr = &a;
	int arr[10] = {0};

	test(&a);
	test(ptr);
	test(arr);

	return 0;
}

注:

1.可以传一个变量的地址,可以传一级指针变量,可以传数组名

2.以后用别人的函数代码时,我们可以知道如何传参

4.4.二级指针传参

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

4.4.2.函数的参数部分为二级指针时,函数能接收什么参数

void test(char** p)
{}

int main()
{
	char ch = 'w';
	char* p = &ch;
	char** pp = &p;
	char* arr[5];

	test(&p);
	test(pp);
	test(arr);

	return 0;
}

注:

1.可以传一个一级指针变量的地址,可以传二级指针变量,可以传一级指针数组的数组名

2.以后用别人的函数代码时,我们可以知道如何传参


5.函数指针

5.1.函数指针

代码1:

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

int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	
	return 0;
}

注:与数组不同,&函数名和函数名拿到的都是函数的地址,函数是没有首元素概念的。

代码2:

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

void test(char* str)
{}

int main()
{

	int arr[5];
	int (*pa)[5] = &arr;  //pa是数组指针

	int (* pf)(int, int) = &Add;  //pf是函数指针
	int (* pf)(int, int) = Add;

	void (*pt)(char*) = test;

	return 0;
}

注:

1.函数也是有数据类型的,如上面代码Add函数的数据类型为int  (int,int),test函数同理。

2.函数指针变量的定义形式为:int (*pf) (int,int),pf是函数指针变量名,pf与*结合说明它是一个指针,该指针是指向数据类型为int  (int,int)的函数地址。test函数同理。

3.Add是函数名,其也是函数的地址,而pf接收的就是函数地址(&Add或Add),因此pf的本质上与Add相同(与int* p=arr,p和arr本质上是相同的同理)

代码3:

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


int main()
{

	int (* pf)(int, int) = Add;

	int sum = (*pf)(2,3);
	int sum = pf(2, 3);
	int sum = Add(2, 3);
	printf("%d\n", sum);

	return 0;
}

注:

1.对函数指针变量解引用(*pf)就得到了该函数,即函数名(Add)

2.因为pf本质上与函数名Add是相同的,因此可以不用解引用,直接pf(2, 3)调用函数。

5.2.两个有趣的代码

代码1:

(*(void (*)())0)();

注:

1.void (*)()是一个函数指针类型,对其加上括号( void (*)() )代表强制类型转换;

( void (*)() )0就是把0强制转换成函数指针类型,地址0处本身数据类型被强制转换成void (*)(),相当于地址0处放了一个类型为void (*)()的函数;

*( void (*)() )0,对其解引用相当于得到了函数名,进而可以调用该函数;

( *( void (*)() )0 )()这是一次函数调用,调用0地址处的函数。调用的时候因为函数指针类型中是无参的,因此后面加的括号内也无参。

2.这是一个特殊的代码,用户是无法直接访问0地址的

代码2:

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

注:

1.括号的优先级高于*,signal首先要和后面的括号结合,因此signal是一个函数名。

signal (int , void(*)(int) ),signal后面括号内没有传参,说明这是一个函数声明,这句代码signal是函数名,函数的第一个参数是整型int类型,第二个参数是函数指针类型;

void ( *signal( int , void(*)(int) ) )(int)除去函数名和函数参数剩下的就应该是返回类型

void ( * )(int)就是这个函数的返回类型,该类型是一个函数指针

 2.可以对该代码进行简化

我们想对void(*)(int)重新起一个名叫pfun_t,此时pfun_t是一个新的函数指针类型的名字。但是我们不能用typedf void(*)(int) pfun_t这种形式定义,而应该typedf void(* pfun_t)(int) 这么定义。注意这里pfun_t不是函数指针变量的名字,而是函数指针类型的名字,如下:

void (*p) (int)    //p是函数指针变量的名字
typedef void(*pf_t)(int)  //pf_t是类型名

此时,就可以用pfun_t代表

void(*)(int)这个指针的类型。简化代码如下:

typedf void(* pfun_t)(int);

pfun_t signal(int,pfun_t)

3.该代码大概使用方式如下:


6.函数指针数组

int* arr[4]     整型指针数组,里面每个元素都是整型

char* arr[5]  字符型指针数组,里面每个元素都是字符型

int (*pfArr[4])(int,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 (*pfArr[4])(int, int) = {Add, Sub, Mul, Div};//函数指针数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		
		int ret = pfArr[i](8, 4);

		printf("%d\n", ret);
	}
	return 0;
}

运行结果:

 注:

上面代码中的int ret = pfArr[i](8, 4)与int ret = (*pfArr[i])(8, 4)本质上是相同的,可以用

int ret = (*pfArr[i])(8, 4)进行替换。

代码2(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 main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		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;
}

代码2(2):(改进版)(※)

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

	int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };//pfArr是一个函数指针的数组,也叫转移表

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);

	return 0;
}

注:

1.pfArr是一个函数指针的数组,也叫转移表

2.用函数指针数组这样改进的前提条件是函数指针数组中各函数的参数个数和类型都是相同的。


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

代码:

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

int main()
{

	int (*pa)(int, int) = Add;  //函数指针

	int (* pfA[4])(int, int);  //函数指针的数组

	int (* (*ppfA)[4])(int, int) = &pfA;  //ppfA 是一个指针,该指针指向了一个存放函数指针的数组

	return 0;
}

注:

1.函数指针数组也是有类型的,如上代码所示,int (*  [4])(int, int)就是函数指针数组的类型。因此如果创建一个存放函数指针数组的指针,*ppfa做变量名,外面加上函数指针数组类型即可。

2.上面代码ppfA 是一个指针,该指针指向了一个存放函数指针的数组


8.回调函数

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

例1:(对计算器代码的改进)(※)

代码:

#include<stdio.h>

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

void calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("输入2个操作数:>");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}

int main()
{
	int input = 0;

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			calc(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;
}

例2:(模拟qsort库函数)

1.qsort函数的介绍:

对上面参数进行简化如下图所示

 

 注:

1.第一个参数base:目标数组的开始,也就是排序数据的起始位置,传给base。

2.第二个参数num:数组的大小,数组元素的个数

3.第三个参数width:数组中每个元素的大小(所占字节数)。

4.第四个参数compare:比较函数,比较函数的返回值满足以下规则

 5.void*是一种无具体类型的指针。

    void*的指针变量可以存放任意类型的地址。

    void*的指针不能直接进行解引用操作,也不能直接进行 +/- 正数操作。

2.使用qsort函数的例子(※)

代码1:

#include<stdio.h>
#include<stdlib.h>

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}


int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}


int main()
{
	int arr[] = { 1,4,2,6,5,3,7,9,0,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);

	print_arr(arr, sz);

	return 0;
}

运行结果:

 注:

1.cmp_int函数本身传入的参数是void*类型的指针,因此要在函数体中对其解引用的话,需要提前强制转换成相应的指针变量类型

2.代码中,*(int*)e1 - *(int*)e2代码是下面代码的简化版

3.要想将升序变成降序,将 *(int*)e1 - *(int*)e2 改成 *(int*)e2 - *(int*)e1 即可。

4.引用qsort函数,需要stdlib.h头文件

代码2(1):(结构体分数比较)

#include<stdio.h>
#include<stdlib.h>


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

int cmp_stu_by_socre(const void* e1, const void* e2)
{
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

int main()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);
	print_stu(arr, sz);
	return 0;
}

运行结果:

 注:

1.这里面比较的话比较的是两个结构体变量,参数传的是两个结构体指针,因此解引用需要强制转换成结构体指针类型,在进行解引用

2.上面代码中,函数体里面比较的代码不能简化成相减的形式(比如0.9-0.8=0.1,而前面的返回类型是一个整型int,0.1返回的就是0,结果错误)

代码2(2):(结构体年龄比较)

#include<stdio.h>
#include<stdlib.h>


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

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

void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

int main()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);

	print_stu(arr, sz);
	return 0;
}

运行结果:

代码2(3):(结构体名字比较)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>


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

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

int main()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	int sz = sizeof(arr) / sizeof(arr[0]);

	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);

	print_stu(arr, sz);
	return 0;
}

运行结果:

 注:

1.strcmp函数的作用是比较字符串的内容(按照字符的ASCII码值进行比较)(从第一个字符开始,若相同则比较第二个字符)

2.strcmp需要string.h头文件

3.模拟实现qsort库函数

代码1(实现整型数组排序):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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 bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	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])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
}

int main()
{
	test1();
	return 0;
}

代码2(实现结构体成绩排序):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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 bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	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])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

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

int cmp_stu_by_socre(const void* e1, const void* e2)
{
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}

void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

void test2()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };

	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);

	print_stu(arr, sz);
}

int main()
{
	test2();
	return 0;
}

代码3(实现结构体年龄排序):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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 bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	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])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

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

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

void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

void test3()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };

	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);

	print_stu(arr, sz);
}

int main()
{
	test3();
	return 0;
}

代码4(实现结构体名字排序):

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

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 bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	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])
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

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

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void print_stu(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}

void test4()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };

	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);

	print_stu(arr, sz);
}

int main()
{
	test4();
	return 0;
}

注:

1.因为base是void*类型,所以不能直接解引用,只有强制类型转换成其他指针类型才能解引用,但是该函数对什么类型排序不能确定,没办法唯一确认一个指针类型。我们使用char*指针类型解引用,并且用width参数可以准确访问到每一个元素。

2.swap函数中,因为不知道是什么类型,我们是使用char*解决的,传参的时候也使用char*,如果要交换的变量数据是多个字节,我们一个一个字节交换即可


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

代码1:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main() 
{

	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));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));

	return 0;
}

x86编译结果:

 x64编译结果:

 注:

1.数组名是数组首元素地址,这里有两个例外:

(1)sizeof(数组名),这里的数组名是表示整个数组的,计算的是整个数组的大小,单位是字节。

(2)&数组名,这里的数组名也表示整个数组,取出的是数组的地址。

除上面两种特殊情况外,所有的数组名都是数组首元素地址

2.sizeof(a+0):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,a+0还是数组首元素地址,是地址大小就是4/8字节

3.sizeof(*a):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,*a是对数组首元素的地址的解引用,就是首元素,首元素是int类型占4个字节

4.sizeof(a+1):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,a+1是第二个元素的地址,是地址大小就是4/8字节

5.sizeof(a[1]):a[1]是数组的第二个元素,大小是4个字节

6.sizeof(&a):&a表示是数组的地址,数组的地址也是地址,地址大小就是4/8字节。

7.sizeof(*&a):&a表示一个数组的地址,类型是int (*) [4] 。*&a相当于对一个数组的地址解引用(对数组指针解引用),得到的是数组的数组名,sizeof(数组名)是数组所占内存大小,大小是16个字节

8.sizeof(&a+1):&a得到的是数组a的地址,数组的地址是第一个元素的地址,&a+1是跳过整个数组16个字节后的地址,是地址就是4/8字节

9.sizeof(&a[0]):&a[0]取出数组第一个元素的地址,是地址就是4/8字节

10.sizeof(&a[0]+1):&a[0]取出数组第一个元素的地址,&a[0]+1是第二个元素的地址,是地址大小就是4/8字节

代码2:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

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

	return 0;
}

x86编译结果:

  x64编译结果:

注:

1.sizeof(arr):arr作为数组名单独放在sizeof内部,计算的整个数组的大小,占6个字节。
2.sizeof(arr + 0):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,arr+0还是数组首元素的地址,地址大小就是4/8字节
3.sizeof(*arr):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,*arr是数组首元素,首元素是一个字符,占1个字节
4.sizeof(arr[1]):arr[1]是数组第二个元素,是一个字符,占1个字节
5.sizeof(&arr):&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节
6.sizeof(&arr + 1):&arr取出的是数组的地址,&arr+1跳过了整个数组,&arr+1还是地址,地址就是4/8个字节
7.sizeof(&arr[0] + 1):&arr[0]是第一个元素的地址,&arr[0] + 1是第二个元素的地址,地址就是4/8个字节

代码3:

#define _CRT_SECURE_NO_WARNINGS 1
#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));
    printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

	return 0;
}

编译结果:(编译结果不固定,*arr和arr[1]因为错误,不进行编译运行)

 

 

注:

1.sizeof只关注占用空间的大小,单位是字节;sizeof不关注类型;sizeof是操作符

   strlen关注字符串中\0的位置,计算的是\0之前出现了多少个字符;strlen只针对字符串;stelen       是库函数

2.strlen(arr):此处arr是数组首元素地址,但是arr数组中没有\0,计算的时候就不知道什么时候停止,结果是随机值1

3.strlen(arr + 0):此处arr是数组首元素地址,arr+0还是数组首元素地址,arr数组中没有\0,结果是随机值1

4.strlen(*arr):strlen需要的是一个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。但*arr是数组的首元素,也就是'a',这时传给strlen的就是'a'的ASCII值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突,如下图。(strlen函数也会对接收的参数进行类型检查,检查的类型不相符,会有间接级别/类型不同警告,如下图)

5. strlen(arr[1]):arr[1]是数组第二个元素,也就是'b',与上一个错误相同,内存访问冲突。

6.strlen(&arr):&arr取出的是数组的地址,此时取出的是起始位置的地址,但是该地址类型为数组指针类型char (*) [6],因为取出的地址是首元素的地址,所以输出的结果是随机值1,因为该地址类型为数组指针类型,与strlen的参数类型有所差异,会报间接级别/类型不同警告

7.strlen(&arr + 1):&arr + 1跳过一个数组, 得到的是arr数组后面字节的地址 , 输出结果是随机值2(随机值2=随机值1-6),该地址类型为数组指针类型,与strlen的参数类型有所差异,会报间接级别/类型不同警告

8.strlen(&arr[0] + 1):&arr[0] + 1得到的是第二个元素的地址,输出的结果是随机值3(随机值3=随机值1-1)

代码4:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <string.h>

int main()
{
	char arr[] = "abcdef";

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

	return 0;
}

x86编译结果:

 x64编译结果:

注:

1.sizeof(arr):arr作为数组名单独放在sizeof内部,计算的整个数组的大小,占7个字节

2.sizeof(arr + 0):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,arr+0还是首元素地址,是地址大小就是4/8字节

3.sizeof(*arr):这里不是数组名单独放在sizeof内部,因此此处数组名是数组首元素地址,*arr是数组第一个元素,该元素是一个字符,占用1个字节

4.sizeof(arr[1]):arr[1]是数组第二个元素,是一个字符,占用1个字节

5.sizeof(&arr):&arr取出整个数组的地址,是地址大小就是4/8字节

6.sizeof(&arr + 1):&arr+1取出的是跳过这个数组,数组后面的地址,是地址大小就是4/8字节

7.sizeof(&arr[0] + 1):&arr[0] + 1取出第二个元素的地址,是地址大小就是4/8字节

代码5:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <string.h>

int main()
{
	char arr[] = "abcdef";
	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));

	return 0;
}

编译结果:(倒数第二个值编译结果不固定,*arr和arr[1]因为错误,不进行编译运行)

 

 

注:

1.strlen(arr):arr表示数组首元素地址,长度为6

2.strlen(arr + 0):arr+0表示数组首元素地址,长度为6

3.strlen(*arr):*arr是数组第一个元素'a',相当于传了字符'a'的ASCII值97,内存访问冲突,系统会报间接级别/类型不同警告

4.strlen(arr[1]):arr[1]是数组第二个元素'b',相当于传了字符'b'的ASCII值98,内存访问冲突,系统会报间接级别/类型不同警告

5.strlen(&arr):&arr取出的是整个数组的地址,整个数组的地址是首元素地址,长度为6;该地址类型为数组指针类型,与strlen的参数类型有所差异,会报间接级别/类型不同警告

6.strlen(&arr + 1):&arr + 1跳过整个数组,指向数组后面地址,因为跳过了\0,因此为随机值

7.strlen(&arr[0] + 1):&arr[0] + 1为第二个元素地址,长度为5

代码6:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <string.h>

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

	return 0;
}

x86编译结果:

 x64编译结果:

 注:

1.sizeof(p):p是一个指针变量,指向字符串第一个字符'a'的地址,sizeof(p)计算的就是指针变量的大小,4/8个字节

2.sizeof(p + 1):p+1指向字符串第二个字符'b'的地址,4/8个字节

3.sizeof(*p):p是char*类型的指针,解引用访问1个字节,也就是字符串第一个元素'a',1个字节

4.sizeof(p[0]):p[0]等价于*(p+0)等价于*p,与上一个代码相同,1个字节

5.sizeof(&p):&p是指针变量p的地址,其类型是char**,是地址就是4/8个字节

6.sizeof(&p + 1):&p是指针变量p的地址,其类型是char**,&p + 1跳过一个地址所占空间即4/8个字节,指向该地址后面的地址,是地址就是4/8个字节

7.sizeof(&p[0] + 1):p[0]等价于*p,*p是第一个元素也就是字符'a',&p[0] + 1是第二个元素'b'的地址,是地址就是4/8个字节

代码7:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <string.h>

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

	return 0;
}

编译结果:(倒数第二个和第三个值编译结果不固定,*p和p[0]因为错误,不进行编译运行)

 注:

1.strlen(p):p是一个指针变量,指向字符串第一个字符'a'的地址,strlen(p)就是从'a'的位置向后求字符串的长度,长度为6

2.strlen(p + 1):p+1得到第二个元素'b'的地址,strlen(p+1)就是从'b'的位置向后求字符串的长度,长度为5

3.strlen(*p):p指向字符串第一个字符'a'的地址,*p是字符串第一个元素'a',也就是将97传给strlen函数,内存访问冲突,系统会报间接级别/类型不同警告

4.strlen(p[0]):p[0]等价于*(p+0)等价于*p,与上面代码相同,内存访问冲突,系统会报间接级别/类型不同警告

5.strlen(&p):&p取出的是指针变量p的地址,传给strlen函数往后计算长度得到一个随机值

6.strlen(&p + 1):&p+1取出的是指针变量p往后跳过四个字节的地址,传给strlen函数往后计算长度得到一个随机值

7.strlen(&p[0] + 1):&p[0] + 1得到的是字符串第二个元素'b'的地址,strlen(&p[0] + 1)就是从'b'的位置向后求字符串的长度,长度为5

代码8:

int main()
{
	//二维数组
	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("%p\n",&a+1);

	return 0;
}

x86编译结果:

 x64编译结果:

 注:

1.sizeof(a):a是二维数组数组名,sizeof(数组名)计算的是整个数组的大小,数组大小为3*4*4=48字节

2.sizeof(a[0][0]):a[0][0]是二维数组第一行第一个元素,大小是4个字节

3.sizeof(a[0]):a[0]是第一行的数组名,a[0]作为数组名单独放在sizeof内部,计算的是整个数组的大小,大小为4*4=16字节

4.sizeof(a[0] + 1):a[0]是第一行的数组名,没有&,没有单独放在sizeof内部,所以a[0]表示的就是首元素地址,即a[0][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是第二行的地址(如下图),其类型为int (*) [4],是地址就是4/8字节

7.sizeof(*(a + 1)):a + 1是二维数组第二行的地址,*(a + 1)是二维数组第二行的数组名,*(a + 1)等价于a[1],sizeof里面是数组名,计算的是第二行的大小,4*4=16字节

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)是二维数组第二行的数组名,也就是a[1],sizeof里面是数组名,计算的是第二行的大小,4*4=16字节

10.sizeof(*a):a是二维数组的数组名,是二维数组首元素地址,二维数组首元素是第一行元素的地址,*a是第一行的数组名,也就是a[0],sizeof里面是数组名,计算的是第一行的大小,4*4=16字节

11.sizeof(a[3]):sizeof不会真的去访问其括号内元素,而是只推导其类型,因此这里感觉a[3]越界了,但是没关系,sizeof得到a[3]类型是int [4],16个字节

12.&a+1:a是二维数组数组名,二维数组的数组名是数组首元素地址,这里的首元素地址是第一行的地址,&a得到的是整个二维数组的地址,&a+1跳过3*4*4=48个字节,指向二维数组后面的地址。


10.指针笔试题

笔试题1:
下面代码的输出结果:
代码:
int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int *ptr = (int *)(&a + 1);
    printf( "%d %d\n", *(a + 1), *(ptr - 1));
    return 0; 
}

运行结果:

 注:

1.&a+1跳过整个a数组,指向该数组后面的地址,其类型为int (*) [5]

2.(int*)(&a+1)赋值给指针变量ptr,ptr还是指向数组后面的地址,类型变为int *类型

3.ptr-1指向数组最后一个元素5的地址,*(ptr-1)得到元素5

4.a是数组名,数组名是首元素地址,类型为int*,*(a + 1)是数组第二个元素2

笔试题2:

p的值为0x100000。 如下表表达式的值分别为多少?
结构体Test类型的变量大小是20个字节

代码:

struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;

int main()
{
 p=(struct Test*)0x100000;
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; 
}

运行结果:

 注:

1.p=(struct Test*)0x100000,0x100000本身是一个十六进制数字,(struct Test*)0x100000将这个十六进制数字的int类型转换成结构体指针类型,此时它就是一个地址,将该地址赋值给p,此时指针变量p指向0x100000这个地址

2.p + 0x1:指针变量+1跳过多少个字节取决于指针变量的类型,int*+1跳过四个字节,char* +1跳过一个字节,p是struct Test*类型,加一跳过20个字节,指向地址0x100014

3.(unsigned long)p + 0x1:此时p被强制转换成unsigned long类型,p不再是一个指针类型,而是一个无符号的整型,此时p里面的值0x100000是一个十六进制数字,(unsigned long)p + 0x1为0x100001

4.(unsigned int*)p+ 0x1:指针p被强制转换成unsigned int*类型,整型指针+1是加4个字节,(unsigned int*)p+ 0x1指向地址0x100004

5.数字以0x开头为十六进制数字,数字以0开头为八进制数字

6.%p是以地址的形式打印,打印出来的十六进制数字前面的0不省略

   %x是打印十六进制数字,打印出来的十六进制数字前面的0会省略

   %p和%x都是无符号打印(该性质与%u相似)

笔试题3:

代码:

int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x %x\n", ptr1[-1], *ptr2);
    return 0; 
}

运行结果:

注:

1.*ptr1 = (int *)(&a + 1):&a + 1跳过整个数组a指向数组后面的地址,(int *)(&a + 1)将该地址的int * [4]强制转换成int*类型,此时指针变量ptr1指向数组a后面的地址,类型为int*

2.打印ptr1[-1]:ptr1[-1]等价于*(ptr1-1),ptr1-1指向数组最后一个元素4的地址,*(ptr1-1)得到a数组最后一个元素4

3.*ptr2 = (int *)((int)a + 1):(int)a,a是数组名,是数组首元素地址,将该地址类型强制转换成整型,得到一个整型数字;(int *)((int)a + 1),数组首元素地址转化成整型变成一个数字再对其加一并转化成指针类型,相当于数组首元素地址往后移动了一个字节,将移动后的地址赋值给*ptr2

vs编译器是小端存储,数组的存储方式如下图所示:

01000000020000000300000004000000

指针变量ptr2指向的是01后面一个字节00的地址,其类型为int*类型,对ptr2解引用访问4个字节,得到的十六进制数字为0x02000000,%x打印去掉前面的0结果应为2000000

笔试题4:

代码:

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

运行结果:

注:

1.该代码数组赋值时里面是圆括号+逗号表达式,a数组如下图所示

13
50
00

2.a[0]是二维数组第一行的数组名,也是第一行第一个元素的地址,指针变量p指向元素1的地址,p[0]等价于*(p+0)等价于*p,因此打印的值为1

笔试题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;
}

运行结果:

 注:

1.二维数组a如下图所示

01234

2.int(*p)[4]:p是一个数组指针,p能够指向的数组是4个整型元素,p+1会跳过4个整型元素16个字节

3.p[4][2]:p里面存的是二维数组a的地址,也就是a的首元素地址即第一行元素的地址。p[4][2]等价于*(*(p+4)+2);*(p+4),指针变量p是int * [4]类型,p+4跳过16个整型元素再进行解引用访问4个整型元素如下图所示

01234

*(p+4)相当于访问的四个整型元素的数组名,数组名是这四个元素第一个元素的地址,*(p+4)+2指向了

这4个元素中第3个元素的地址,*(*(p+4)+2)就指向了这四个元素中的第三个元素,如下图所示

01234

因此p[4][2]就是上图的红方格元素,&p[4][2]就是该元素的地址

4.a[4][2]是数组第5行第3个元素,如下图所示

01234

因此a[4][2]就是上图的红方格元素,&a[4][2]就是该元素的地址

5. &p[4][2] - &a[4][2]:两个整型的地址相减,得到的是两个地址之间int元素的个数,因为是小地址减去大地址,所以结果为-4

01234

6.-4的原码:10000000000000000000000000000100

   -4的反码:11111111111111111111111111111011

   -4的补码:11111111111111111111111111111100

   -4补码的十六进制为(%p打印):ff ff ff fc 

笔试题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\n", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}

运行结果:

 注:

1.int* ptr1 = (int*)(&aa + 1):&aa+1跳过整个数组指向数组后面的地址,将该地址强制类型转换成int*类型并赋值给ptr1。指针变量ptr1指向数组后面元素的地址,类型为int*

2.*(ptr1 - 1):ptr1-1指向了数组最后一个元素的地址,对其解引用得到了数组最后一个元素10

3.int* ptr2 = (int*)(*(aa + 1)):aa是二维数组的数组名,是二维数组第一行元素的地址;aa + 1跳过第一行,指向第二行的地址,其类型为int * (5);*(aa + 1)得到了二维数组第二行得数组名,也相当于第二行首元素地址,因此得到了第二行首元素6的地址;(int*)(*(aa + 1))此处强制类型转换成int*类型是多余的,因为其本身就是int*类型;将(int*)(*(aa + 1))赋值给指针变量ptr2,ptr2指向二维数组第二行首元素6的地址,类型为int *类型。

4.*(ptr2 - 1):ptr2-1指向了二维数组元素5的地址,对其解引用得到了元素5

笔试题7:

代码:

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

运行结果:

注:

1.指针数组a和二级指针变量pa关系如下图所示

2.二级指针变量pa的类型为char**类型,其指向对象为char*类型,因此pa+1或解引用跳过或访问4个字节,pa++指向指针数组第二个元素,*pa就是指针数组a的第二个元素,指针数组a的第二个元素是字符串"at"的地址,因此给打印函数传参*pa相当于传了"at"字符串的地址即数组名,打印的结果为at

笔试题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;
}

运行结果:

 注:

1.指针数组c、二级指针变量cp和三级指针变量cpp关系如下图所示

2.**++cpp:cpp此时指向cp第一个元素地址;++cpp跳过一个char**的元素4个字节,指向cp第二个元素地址;*++cpp得到cp第二个元素,该元素为c+2,即指针数组c第三个元素的地址;**++cpp得到了指针数组c的第三个元素,该元素为字符串"POINT"的地址;将字符串的地址传参给printf函数进行打印(注意:**++cpp代码执行完,cpp存的是cp第二个元素的地址,指向了cp的第二个元素)

3.*-- * ++cpp + 3:cpp此时指向cp第二个元素地址;++cpp跳过一个char**的元素4个字节,指向cp第三个元素地址;* ++cpp得到cp第三个元素,该元素为c+1,即指针数组c第二个元素的地址;-- * ++cpp为cp第三个元素-1,即c+1-1=c,也就是指针数组c第一个元素的地址;*-- * ++cpp得到了指针数组c的第一个元素,该元素为字符串"ENTER"的地址;*-- * ++cpp + 3指向了字符串"ENTER"中字符'E'的地址;将字符串中'E'的地址传参给printf函数进行打印(注意:*-- * ++cpp + 3代码执行完,cpp存的是cp第三个元素的地址,指向了cp的第三个元素)

4.*cpp[-2] + 3:cpp[-2]等价于*(cpp-2),cpp本身是指向cp第三个元素,cpp-2指向了cp第一个元素,*(cpp-2)得到了cp第一个元素,该元素为c+3,即指针数组c第四个元素的地址;*cpp[-2]得到了指针数组c的第四个元素,该元素为字符串"EIRST"的地址;*cpp[-2] + 3指向了字符串"FIRST"中字符'S'的地址;将字符串中'S'的地址传参给printf函数进行打印(注意:*cpp[-2] + 3代码执行完,cpp存的是cp第三个元素的地址不变,指向了cp的第三个元素)

5.cpp[-1][-1] + 1:cpp[-1][-1]等价于*(*(cpp-1)-1),cpp本身是指向cp第三个元素,cpp-1指向了cp第二个元素,*(cpp-1)得到了cp第二个元素,该元素为c+2,即指针数组c第三个元素的地址,*(cpp-1)-1为cp第二个元素-1,即c+2-1=c+1,也就是指针数组c第二个元素的地址,*(*(cpp-1)-1)得到了指针数组c的第二个元素,该元素为字符串"NEW"的地址;cpp[-1][-1] + 1指向了字符串"NEW"中字符'E'的地址;将字符串中'E'的地址传参给printf函数进行打印

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随风张幔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值