C语言—指针的进阶

news2024/12/24 3:05:51

指针的进阶

  • 字符指针
  • 指针数组
  • 数组指针
    • 数组指针的定义
    • 区分&数组名以及数组名
    • 数组指针的使用
  • 数组参数、指针参数
    • 一维数组传参
    • 二维数组传参
    • 一级指针传参
    • 二级指针传参
  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针
  • 回调函数
  • 指针和数组相关笔试题和面试题
    • 指针笔试题

指针的相关概念:

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

字符指针

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

使用方法:

int main()
{
	char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}
int main()
{

	//本质上是把"hello world"这个字符串的首字符的地址存储在了ps中

	char* ps = "hello world";

	//本质是把"hello world"放到arr数组里
	char arr[] = "hello world";


	return 0;
}

注:上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量ps中

经典面试题:

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    char *str4 = "hello bit.";
    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;
}

运行结果:

在这里插入图片描述
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

指针数组

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

指针数组的定义:

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

指针数组的使用:

int main()
{
	//指针数组
	//数组 - 数组中存放的是指针(地址)
	//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]));
	}
	return 0;
}



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

数组指针

数组指针的定义

整形指针: int * pint; 能够指向整形数据的指针。 浮点型指针: float * pf; 能够
指向浮点型数据的指针。数组指针能够指向数组的指针

数组指针的定义:

int main()
{
	int a = 10;
	int*pa = &a;    //整型指针 - 是指向整形的指针
	char ch = 'w';  
	char*pc = &ch;  //字符指着 - 是指向字符的指针


	//数组指针是一种指针 - 是指向数组的指针
	double* d[5];

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

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

	//parr 就是一个数组指针 - 其中存放的是数组的地址

	//arr - 数组名是首元素的地址 - arr[0]的地址
	return 0;
}

注:

  • double* (*pd)[5]——pd先和 * 结合,说明pd是一个指针变量,然后指针指向的是一个大小为5个double * 类型的指针数组。所以pb是一个指针指向一个数组是数组指针。
  • []的优先级要高于 * 号的,所以必须加上()来保证指针变量先和*结合。

区分&数组名以及数组名

实例一:

在这里插入图片描述

运行结果:数组名和&数组名打印的地址是一样的。

分析:数组名表示的是数组首元素的地址其实&arr和arr,虽然值是一样的,但是意义应该不一样的。实际上 &arr 表示的是数组的地址(取出的是数组的起始地址),而不是数组首元素的地址。此外,arr的类型是int* 的,而&arr的类型是int (*)[10]的。数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

注:

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

数组指针的使用

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

实例一:

在这里插入图片描述

数组指针的应用场景

实例二:

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

//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));   
			//*(p+i)表示的是第i行的数组名相当于arr[i](第i行的数组名相当于第i行首元素的地址)
			//*(p + i) + j表示的是第i行中下标为j元素的地址
			//*(*(p + i) + j)访问的是第i行第j列的元素
		}
		printf("\n");
	}
}

int main()
{
	//int a[5];  &a
	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;
}

注:

  • 二维数组的数组名表示数组首元素的地址
  • 二维数组的首元素是第一行。因为把二维数组理解为一维数组的时候,把它的第一行、第二行、第三行看成一个个元素,这时二维数组才有首元素
  • 二维数组的数组名表示数组中第一行的地址,也就是一维数组的地址
int arr[5][5];
int(*parr)[5][5]=&a;  //parr为二维数组指针

实例三:

int arr[5];      
//整形数组,数组五个元素,每个元素为int类型
int *parr1[10];  
//整形指针数组,数组十个元素,数组的每个元素是int*类型指针 
int (*parr2)[10];
//数组指针,该指针能够指向一个数组,数组十个元素每个元素的类型是int类型
int (*parr3[10])[5];
//parr3是一个存储数组指针的数组,该数组能能够存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型

数组参数、指针参数

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

一维数组传参

#include <stdio.h>
void test(int arr[])//数组传参数组接收,形参部分中的数组大小可以不写,因为本质上传递过来的是数组首元素的地址。形参可以写成数组的形式
{}
void test(int arr[10])//这里的10是没有任何意义的,这种书写方式可以
{}
void test(int *arr)//数组名传参传的是数组首元素的地址
{}
void test2(int *arr[20])//数组传参参数写成数组,因为实参是指针的数组,20可以省略
{}
void test2(int **arr)//数组名表示首元素地址
{}
int main()
{
 	int arr[10] = {0};
 	int *arr2[20] = {0}; //整形指针数组
 	test(arr);
 	test2(arr2);
 	return 0;
}

总结:一维数组传参可以写成一维数组,数组大小可以省略也可以不写,也可以写成指针。如果一维数组是个指针数组也可以写成指针数组或者写成二级指针都可以。

二维数组传参

void test(int arr[3][5])//数组传参实参写成数组形参也是数组
{}
void test(int arr[][])//二维数组行可以省略列不行,这种写法是错误的
{}
void test(int arr[][5])//二维数组中行是可以省略的
{}

//注:二维数组传参可以把参数部分写成数组,行和列可以不省略也可以省略,但是一定只能省略行不能省略列

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

void test(int *arr)//用一级指针接收是不行的,这种写法是错误的
{}
void test(int* arr[5])//用指针数组接收是不行的,这种写法是错误的
{}
void test(int (*arr)[5])//实参是二维数组的数组名时,这里写成指针时一定是指向一维数组的指针
{}
void test(int **arr)//实参传递过去的不是二级指针,这种写法是错误的
{}
int main()
{
 	int arr[3][5] = {0};
 	test(arr);//二维数组传参时数组名表示首元素的地址指的是第一行的地址(一维数组的地址)
}

一级指针传参

#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 test(char* p)
{}

int main()
{
	char ch = 'w';
	char* p = &ch;
	
	//test函数可以接收地址
	test(&ch);//char*
	//test函数可以接收一级指针
	test(p);

	return 0;
}

二级指针传参

void test(int** p2)
{
	**p2 = 20;
}

int main()
{
	int a = 10;
	int* pa = &a;//pa是一级指针
	int** ppa = &pa;//ppa是二级指针
	
	//把二级指针进行传参
	test(ppa);
	printf("%d\n", a);//20

	return 0;
}

注:当函数的参数为二级指针的时候,可以接收的参数为二级指针变量、一级指针变量的地址、存放一级指针的数组的数组名

实例一:

void test(int** p2)
{
	**p2 = 20;
}

int main()
{
	int a = 10;
	int* pa = &a;//pa是一级指针
	int** ppa = &pa;//ppa是二级指针
	
	test(ppa);//把二级指针进行传参
	test(&pa);//传一级指针变量的地址

	int* arr[10] = {0};
	test(arr);//传存放一级指针的数组
	printf("%d\n", a);//20

	return 0;
}

函数指针

函数指针:指向函数的指针,存放函数地址的指针。

函数指针的定义:

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

int main()
{
	int a = 10;
	int* pa = &a;

	char ch = 'w';
	char* pc = &ch;

	int arr[10] = {0};
	int (*parr)[10] = &arr;//取出数组的地址
	//parr 是指向数组的指针 - 存放的是数组的地址

	//函数指针 - 存放函数地址的指针
	//pf就是一个函数指针变量
	int (*pf)(int, int) = &Add;
	//pf先和*结合,说明pf是指针,指针指向的是一个函数,指向的函数参数有两个并且都是整型类型,返回值类型为int。
	
	//&函数名(函数名) - 取到的就是函数的地址 
	printf("%p\n", &Add);
	printf("%p\n", Add);

	return 0;
}

注:

  • &函数名和函数名是一个意思代表的含义相同(代表的是函数的地址)
  • 函数调用操作符比间接访问操作符优先级高

补充:函数指针中指针所指向的函数参数部分中的形参名(没有意义)可以省略也可以写上,但形参的类型不可以省略

函数指针的使用:

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

int main()
{
	//pf就是一个函数指针变量
	//int (*pf)(int, int) = &Add;

	int (*pf)(int, int) = Add;//Add === pf
	int ret = (*pf)(3, 5);//这种写法中的*是没有意义的(*的个数不限)
	//这个*放在这只是为了理解起来方便,但是没有任何意义(没有实际上的运算意义)
	
	//int ret = pf(3, 5);
	//int ret = Add(3, 5);

	//int ret = * pf(3, 5);//这种写法是err

	printf("%d\n", ret);

	return 0;
}

实例一:

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

注:调用0地址处的函数,该函数无参,返回类型是void。

分析:

  1. void(*)() - 函数指针类型
  2. (void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
  3. (void()())0 - 对0地址进行了解引用操作
  4. ((void()())0)() - 调用0地址处的函数

实例二:

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

分析:

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

实例二的简化:

//typedef - 对类型进行重定义(重命名)

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

注: 函数的返回类型是函数指针类型,其中*必须要和函数名放在一起

函数指针数组

数组是一个存放相同类型数据的存储空间

函数指针数组是存放函数指针的数组

函数指针数组的定义:

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 (*pfArr[2])(int, int) = {pf1, pf2};  //也可以这样写
	return 0;
}

函数指针数组的用途:转移表

计算器

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;
	//计算器-计算整型变量的加、减、乘、除
	

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

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

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

		//pfArr就是函数指针数组
		//pfArr是转移表 

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


通过下标找到函数指针数组中某个元素,这个元素恰好是一个函数的地址,然后调用这个地址所对应的函数,通常把这样的函数指针数组称为转移表

注: 相比于第一种计算器实现这样写可以避免代码冗余以及增加计算器功能会导致代码量的增大(不便阅读)

指向函数指针数组的指针

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

指向函数指针数组的指针的定义:

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)[10])(const char*) = &pfunArr;
 	return 0;
}

注:int arr[10]中的数组元素类型时int,arr数组的类型是int[10]

回调函数

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

实例一:

用回调函数实现计算器

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;
	//计算器-计算整型变量的加、减、乘、除

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

注:使用回调函数可以减少代码冗余

实例二:

qsort函数的相关介绍:

在这里插入图片描述

void qsort(void* base, //base中存放的是待排序数据中第一个对象(元素)的地址
			size_t num, //排序数据元素的个数
			size_t size,//排序数据中一个元素的大小,单位是字节
			int (*cmp)(const void* , const void* )//是用来比较待排序数据中的2个元素的函数
			);

qsort函数的使用:

//整形数据的排序

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

void test1()
{
	//整形数据的排序
	int arr[] = { 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);
	//打印
	print_arr(arr, sz);
}

//结构体数据的排序

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

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

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

void test2()
{
	//使用qsort函数排序结构体数据
	struct Stu s[3] = { {"zhangsan", 30},{"lisi", 34},{"wangwu", 20} };

	int sz = sizeof(s) / sizeof(s[0]);
	//按照年龄来排序
	qsort(s, sz, sizeof(s[0]), sort_by_age);
	//按照名字来排序
	qsort(s, sz, sizeof(s[0]), sort_by_name);
}

注:

  • qsort库函数底层用的是快速排序的思想实现的
  • void*的指针是一种无类型的指针,不可以对它进行解引用,什么类型的地址都可以放入到无类型的指针中。
  • strcmp库函数是比较对应位置上的字符的ASCII值
  • void类型的指针作用是用来存放任意指针类型的变量(数据)(任意类型指针的值),但是不可以进行指针解引用操作(因为void是无类型的指针,解引用访问几个字节是不确定的。指针+整数也是一样的道理)。因此要使用void类型的指针就需要提前对该指针进行强制类型转化再进行使用

qsort的模拟实现(采用冒泡排序的思想):

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

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

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 (cmp( (char*)base+j*width, (char*)base+(j+1)*width )>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

指针和数组相关笔试题和面试题

//一维数组
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));    //计算的是数组的总大小(因为&a得到的是数组的地址,对它进行解引用得到的是整个数组的大小。相当于计算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




//字符数组
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


printf("%d\n", strlen(arr));      //从首元素地址开始计算  随机值
printf("%d\n", strlen(arr+0));    //从首元素地址开始计算  随机值
printf("%d\n", strlen(*arr));     //strlen函数传参时实参不能传字符类型变量(访问内存冲突(编译器)(非法访问内存))(因为strlen函数将传过来的实参强制类型转换成const char*类型的地址,将a的ASCII值97传给strlen参数作为形参的地址)  报错
printf("%d\n", strlen(arr[1]));   //strlen函数传参时实参不能传字符类型变量(访问内存冲突(编译器))(因为strlen函数将传过来的实参强制类型转换成const char*类型的地址,将b的ASCII值98传给strlen参数作为形参的地址)  报错
printf("%d\n", strlen(&arr));     //在strlen函数看来strlen(&arr)和strlen(arr)以及strlen(arr+0)没有本质区别,将&arr的地址传递给strlen参数,strlen函数会对传递过来的实参进行强制类型转换转换成const char*类型  随机值
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));        //从数组中的第一个元素地址开始计算遇到'\0'结束  6
printf("%d\n", strlen(arr+0));      //从数组中的第一个元素地址开始计算遇到'\0'结束  6
printf("%d\n", strlen(*arr));       //strlen函数传参时实参不能传字符类型变量(访问内存冲突(编译器))(因为strlen函数将传过来的实参强制类型转换成const char*类型的地址,将a的ASCII值97传给strlen参数作为形参的地址)  报错
printf("%d\n", strlen(arr[1]));     //strlen函数传参时实参不能传字符类型变量(访问内存冲突(编译器))(因为strlen函数将传过来的实参强制类型转换成const char*类型的地址,将b的ASCII值98传给strlen参数作为形参的地址)  报错
printf("%d\n", strlen(&arr));       //在strlen函数看来strlen(&arr)和strlen(arr)以及strlen(arr+0)没有本质区别,将&arr的地址传递给strlen参数,strlen函数会对传递过来的实参进行强制类型转换转换成const char*类型  6
printf("%d\n", strlen(&arr+1));     //从跳过整个数组之后下一块空间的起始地址开始计算  随机值
printf("%d\n", strlen(&arr[0]+1));  //从数组中的第二个元素地址开始计算遇到'\0'结束  5




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));      //计算的是地址的大小-计算的是字符指针变量p的地址  4/8
printf("%d\n", sizeof(&p+1));    //计算的是地址的大小-计算的是从跳过字符指针变量p之后下一块空间的起始地址  4/8
printf("%d\n", sizeof(&p[0]+1));    //计算的是地址的大小-计算的是字符串中第二个字符地址的大小  4/8

printf("%d\n", strlen(p));      //从字符串中的第一个字符地址开始计算遇到'\0'结束  6
printf("%d\n", strlen(p+1));    //从字符串中的第二个字符地址开始计算遇到'\0'结束  5
printf("%d\n", strlen(*p));     //strlen函数传参时实参不能传字符类型变量(访问内存冲突(编译器))(因为strlen函数将传过来的实参强制类型转换成const char*类型的地址,将a的ASCII值97传给strlen参数作为形参的地址)  报错
printf("%d\n", strlen(p[0]));   //strlen函数传参时实参不能传字符类型变量(访问内存冲突(编译器))(因为strlen函数将传过来的实参强制类型转换成const char*类型的地址,将a的ASCII值97传给strlen参数作为形参的地址)  报错
printf("%d\n", strlen(&p));     //从指针变量p的起始地址开始计算  随机值
printf("%d\n", strlen(&p+1));   //从跳过字符指针变量p之后下一块空间的起始地址计算  随机值(注:strlen(&p)和strlen(&p+1)这两个随机值完全没有关系,因为根本不知道指针变量p中存放的是什么(指针变量p可能是4字节或者8字节),如果这个指针变量内部就有'\0'那么前面取出得值一定是小于4的,而从跳过字符指针变量p之后下一块空间的起始地址向后遇到的'\0'这个长度有可能会长一些。如果这个指针变量内部没有'\0'那么前面随机值一定长于后面的随机值)
printf("%d\n", strlen(&p[0]+1));//从字符串中的第二个字符地址开始计算遇到'\0'结束  5


//二维数组
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));     //计算的是地址的大小-计算的是二维数组中第一行第二列元素的地址的大小(a[0]作为数组名并没有单独放在sizeof内部,也没取地址,所以a[0]表示的就是第一行第一列元素的地址,而a[0]+1表示的就是第一行第二列元素的地址)  4/8          
printf("%d\n",sizeof(*(a[0]+1)));  //计算的是数组元素的大小-数组中第一行第二列元素的大小  4
printf("%d\n",sizeof(a+1));        //计算的是地址的大小-计算的是二维数组中第二行一维数组的地址的大小(a是二维数组的数组名,并没有取地址也没有单独放在sizeof内部,所以a就表示二维数组首元素的地址即第一行的地址,而a + 1就是二维数组第二行的地址)  4/8
printf("%d\n",sizeof(*(a+1)));     //计算的是数组的大小-二维数组中第二行一维数组的总大小(a+1是第二行的地址,所以*(a+1)(a[1])表示的是第二行,因此计算的就是第2行的大小)  16
printf("%d\n",sizeof(&a[0]+1));    //计算的是地址的大小-计算的是二维数组中第二行一维数组的地址的大小(a[0]是第一行的数组名,&a[0]取出的就是第一行的地址,&a[0]+1 表示的就是第二行的地址,sizeof(&a[0]+1)和sizeof(a+1)表示的意思一样没有区别)  4/8
printf("%d\n",sizeof(*(&a[0]+1))); //计算的是数组的大小-二维数组中第二行一维数组的总大小(&a[0]+1表示的就是第二行的地址,*(&a[0]+1) 就是第二行,所以计算的第二行的大小,sizeof(*(&a[0]+1))和sizeof(*(a+1))表示的意思一样没有区别)  16
printf("%d\n",sizeof(*a));         //计算的是数组的大小-二维数组中第一行一维数组的总大小(a作为二维数组的数组名,没有&也没有单独放在sizeof内部,a表示的就是首元素的地址即第一行的地址,所以*a就是第一行,计算的是第一行的大小。*a=*(a+0)=a[0])  16
printf("%d\n",sizeof(a[3]));       //计算的是数组的大小-二维数组中第四行一维数组的总大小(a[3]其实是第四行的数组名(如果有的话)所以其实不存在,也能通过类型计算大小的。sizeof()内部的表达式是不会计算的,它会通过表达式的类型属性进行推测从而计算sizeof中表达式的结果,在sizeof(a[3])中a[3]的类型是int[4],sizeof并不会访问a[3]数组从而计算出大小,它会根据a[3]的类型进行推测从而计算出a[3]的大小)  16(并不会报错)   

总结: 数组名的意义:

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

指针笔试题

实例一:

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+1的类型是int(*)[5],将&a + 1的值赋值给int *ptr需要强制类型转换

实例二 :

//已知该结构体的大小是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);  //0x100014
 	printf("%p\n", (unsigned long)p + 0x1);  //0x100001
 	printf("%p\n", (unsigned int*)p + 0x1);  //0x100004
 	return 0;
}

注:

  • 指针类型决定了指针的运算(±整数的运算)
  • 第一个表达式是结构体类型指针与整数运算
  • 第二个表达式是将指针类型强制类型转换成无符号的长整型之后再与整数进行运算
  • 第三个表达式是将指针类型强制类型转换成无符号整型指针之后再与整数进行运算

实例三:

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

注:

  • ptr2所指向的位置位于数组首元素的起始地址的下一个字节处
  • %x输出的16进制;%#x可以输出0x的十六进制(不包含高位是零)
  • 每一个字节都给一个地址

实例四:

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };  //逗号表达式
    //二维数组中放的元素是1 3 5 0 0 0
    int *p;
    p = a[0];
    printf( "%d", p[0]);  //1
 	return 0;
}

注:

  • a[0]代表的是二维数组中第一行数组首元素的地址(第一行第一列的元素地址)

实例五:

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打印地址,打印时认为内存里存的是地址,内存里的补码直接解析成原码

实例六:

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

注:

  • *(aa + 1)表示的是二维数组中第二行数组的首元素的地址
  • int * ptr2 = (int * )(*(aa + 1));中的 (int * )是没有意义,因为 * (aa + 1)表示的就是整型元素的地址不需要强制类型转换再赋值给ptr2指针变量

实例七:

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

在这里插入图片描述

实例八:

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

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/479615.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ Boost.Reflection库(现在是Boost.PFR)的下载、安装、使用详细教程

这里写目录标题 一、Boost.Reflection简介二、Boost.Reflection&#xff08;现在是Boost.PFR&#xff09;库的下载和安装2.1、下载编译Boost2.2、使用Boost.Reflection&#xff08;现在是Boost.PFR&#xff09; 小结 一、Boost.Reflection简介 ​ Boost.Reflection&#xff0…

【C++】-关于类和对象的默认成员函数(中)-构造函数和析构函数

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树 ❤️‍&#x1fa79;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a8;作者gitee:gitee &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点…

Cadence(2):向导制作PCB封装

前提&#xff1a;软件版本 焊盘设计 &#xff1a;Pad Designer16.6PCB设计 &#xff1a;PCB Editor16.6 文章目录 LQFP64向导封装制作封装信息SMD焊盘制作添加焊盘库到PCB Editor新建工程LQFP64向导制作流程后续处理修改栅格大小。贴加1脚标识修改丝印线 总结 LQFP64向导封装制…

获取代码量(针对Pycharm IDEA)

Statistic 这是个一劳永逸&#xff0c;获取代码量的方法。 Beginning!&#xff08;安装到使用可能都不需要5分钟&#xff09; 附上Statistic的官网下载URL&#xff1a;Statistic - IntelliJ IDEs Plugin | Marketplacehttps://plugins.jetbrains.com/plugin/4509-statistic/v…

clion + opencv环境搭建

clion是一个jetbrains提供的c开发环境&#xff0c;和idea,pycharm等开发工具类似&#xff0c;界面有很多相似的地方。 clion内置了一个mingw的编译环境&#xff0c;自带了gcc,g等命令&#xff0c;安装完clion之后&#xff0c;我们新建项目&#xff0c;它会使用内置的mingw环境。…

在KylinV10上使用DTS进行mysql8迁移至DM8

前言&#xff1a; MySQL 到 DM 的移植主要有以下几个方面的工作&#xff1a; 1&#xff0e;分析待移植系统&#xff0c;确定移植对象。2&#xff0e;通过数据迁移工具 DTS 完成常规数据库对象及数据的迁移。3&#xff0e;通过人工完成 MSQL 的移植。4&#xff0e;移植完成后对移…

从设计到产品

从设计到产品 最近上的一些课的笔记&#xff0c;从 0 开始设计项目的角度去看产品。 设计系统 设计系统(design system) 不是 系统设计(system design)&#xff0c;前者更偏向于 UI/UX 设计部分&#xff0c;后者更偏向于实现部分。 个人觉得&#xff0c;前端开发与 UI/UX 设…

使用 ESP32 设计智能手表 – 第 1 部分制作表盘

相关设计资料下载ESP32 智能手表带心率、指南针设计资料(包含Arduino源码+原理图+Gerber+3D文件).zip 人们可以使用智能手表轻松快速地访问消息、警报、健康信息和其他高级信息。虽然智能手表作为独立设备在形式上是革命性的,但当与人们携带的其他设备(例如智能手机或平板…

作为一个大学生你应该知道的事情

作为一个大学生你应该知道的事情 大学生毕业去向 今天&#xff0c;我们不写技术&#xff0c;来谈一谈大学生的毕业现状&#xff1a; 以下内容为本人的一些观点和看法&#xff0c;仅限于沟通交流。 大学生毕业去向 大学生的毕业去向大致可以分为&#xff1a;就业、自由职业、慢…

Python之常用设计模式

1、 设计模式 2、接口 interface.py #! /usr/bin/env python # -*- coding: utf-8 -*- # Date: 2018/12/1# class Payment: # def pay(self, money): # raise NotImplementedErrorfrom abc import ABCMeta, abstractmethod# 接口 # class Payment(metaclassABCMet…

低频量化之 可转债 配债数据及策略 - 全网独家

目录 历史文章可转债配债数据 待发转债&#xff08;进展统计&#xff09;待发转债&#xff08;行业统计&#xff09;待发转债&#xff08;5证监会通过&#xff0c;PE排序&#xff09;待发转债&#xff08;5证监会通过&#xff0c;安全垫排序&#xff09;待发转债&#xff08;5证…

JVM--一文精通

调整JVM堆内存 在确定JVM堆内存大小时&#xff0c;需要考虑以下因素&#xff1a; 应用程序的内存需求。操作系统和其他应用程序所需的内存。JVM的运行参数和GC算法。 根据通常的经验&#xff0c;可以将JVM最大堆内存设置为操作系统可用内存的约70%。也就是说&#xff0c;在1…

Kube-OVN组件

文章目录 介绍ovn架构kube-ovn架构kube-ovn数据流向软件版本及注意事项高可用部署安装kube-ovn运维查看Pod日志组件监控kubectl ko插件 网络相关 介绍 kube-ovn是基于ovn开发的。https://man7.org/linux/man-pages/man7/ovn-architecture.7.html ovn架构 组件说明&#xff1a…

PySide6/PyQT多线程之 信号与槽 / (Signal Slot)的高效利用

前言 PySide6/PyQT信号槽是一种事件处理方式&#xff0c;允许程序中的对象发送和接收信号。 在 PySide6/PyQT 精进的过程中&#xff0c;一定躲不开 信号和槽 这座大山&#xff0c;这是一个比较有意思的知识点&#xff1a; 初接触的看不懂&#xff0c;觉得复杂&#xff1b;看得…

【Linux】win10远程控制Linux服务器 - 内网穿透实现公网远程

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 视频教程 1. ubuntu安装XRDP 2.局域网测试连接 3. Ubuntu安装cpolar内网穿透 4.cpolar公网地址测试访问 5.固定域名公网地址 [TOC] 转载自远程穿透文章&#xff1a;Wi…

Redis的两种持久化方案 RDB AOF

文章目录 1.RDB持久化1.1.执行时机1.2.RDB原理1.3.小结 2.AOF持久化2.1.AOF原理2.2.AOF配置2.3.AOF文件重写 3.RDB与AOF对比 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09…

【三十天精通Vue 3】 第二十三天 Vue 3的错误处理详解

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: 三十天精通 Vue 3 文章目录 引言一、Vue 3 错误处理概览1. 错误处理的重要性2. Vue 3 中的错误…

EMS快递批量分析物流信息状况

众所周知“邮政快递”是在快递行业里面也是算一家行业龙头&#xff0c;中国邮政特快专递/EMS&#xff0c;邮政平邮小包&#xff0c;邮政挂件等&#xff0c;都是属于中国邮政集团&#xff0c;邮政速度快&#xff0c;价格也是比较实惠一家&#xff0c;所以很多产家或电商 、供应商…

大数据-玩转数据-FLINK快速上手

一、环境准备 ⚫系统环境为 Windows 10。 ⚫需提前安装 Java 11。 ⚫集成开发环境&#xff08;IDE&#xff09;使用 IntelliJ IDEA&#xff0c;具体的安装流程参见 IntelliJ 官网。 ⚫安装 IntelliJ IDEA 之后&#xff0c;还需要安装一些插件——Maven 和 Git。Maven 用来管理…

Java实现宿舍管理系统的设计与实现【附源码】

本科生毕业论文&#xff08;设计&#xff09; 宿舍管理系统的设计与实现 目 录 摘 要 I Abstract II 1 引言 1 1.1 研究背景 1 1.2 国内外研究现状 1 1.3 研究目的与意义 1 2 开发工具和相关技术 2 2.1 Eclipse 2 2.2 MySQL 2 2.3 Bootstrap 3 2.4 Tomcat 3 3 系统分析 3 3.1…