IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C语言】_9.指针进阶 -> 正文阅读

[C++知识库]【C语言】_9.指针进阶

目录???????

1.字符指针

?2.指针数组

?3.数组指针

4.数组参数、指针参数

5.函数指针

?6.函数指针数组

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

8.回调函数

9.例题

10.笔试题


正文:

前面我们已经对指针有了初步了解,此处做简单回顾:

(1)内存会划分为小的内存单元,每个内存单元会有一个编号,这个编号也被称为地址,把存放地址的变量称为指针变量。即:内存编号=地址=指针,它们都是一个数值。我们常说的int* p;把p称为一个指针,其实就是一个指针变量的意思;

(2)指针的大小是固定的4或8个字节(32位或64位平台);

(3)指针是有类型的,指针的类型决定了指针+整数的步长和指针解引用操作时的权限;

(4)指针的运算:指针+-整数,指针-指针,指针的关系运算。


1.字符指针

一般有两种使用方式:

第一种:

#include<stdio.h>
int main()
{
	char ch = 'w';
	char* pc = &ch;
	*pc = 'a';
	printf("%c\n", ch);
	return 0;
}

第二种:

我们知道在×86环境下,指针变量p是4个字节,故而p中存放的不可能是"abcdef"的字符串。当我们对p进行解引用操作并打印,可以发现输出结果为a,也就是说,指针变量p存放的是该字符串瘦首字符a的地址。如果想打印字符串,则输出语句应改为:printf("%s\n",p);

同时应注意,“abcdef”作为常量字符串,是不能被修改的,如果强行进行类似于*p='w'这种操作,代码将无法运行。为了避免这种情况,我们可以在创建字符指针前进行const修饰,即:

const char* p="abcdef";

然后如果再进行*p='w'强行修改的操作,那么编译器会报错。而且在部分编译器中,如果不加const修饰会报警告;

练习:

?2.指针数组

顾名思义,指针数组就是存放指针的数组

int* arr[10];  //存放整型指针的数组
char* ch[5];   //存放字符指针的数组

应用示例:

?但是在实际应用中,很少会将元素地址刻意存放到一个数组中。此处只是作为示例。

?更常用的如下例所示:

?3.数组指针

3.1 定义

首先明确数组指针是一个指针。

int a=10;
int* p=&a;
//整形指针是指向整型的指针,用于存放整型变量地址
char ch='w';
char* pc=&ch;
//字符指针是指向字符的指针,用于存放字符变量地址

?故而我们可以类推,数组指针即是指向数组的指针。

//试区分哪一个是数组指针
int* p1[10];
int(*p2)[10];

?

3.2 区别&数组名(&arr)与数组名(arr)的区别

再举一例:?

char* arr[5];
p=&arr;

//∴char (*p)[5]=&arr

再次回顾数组名的含义:

通常情况下数组名都表示首元素地址,两个例外:①sizeof(数组名)计算的是整个数组的大小;②&(数组名)取出的是整个数组的地址;

3.3 数组指针的使用

首先看一组一维数组的示例:

现要求设计代码打印一个一维数组的元素:

法一:形参为指针(也可做形参为数组,比较简单,此处不做演示)

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

?这也是我们常用的方法;

法二:使用数组指针:

#include <stdio.h>
void print1(int(*p)[10], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *((*p) + i));
		//*p相当于数组名
	}
}
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);
	return 0;
}

也可以实现要求,但是会发现,步骤繁琐并且大可不必使用数组指针。

通常在使用一维数组时,我们很少会使用数组指针。

接下来看一组二维数组的示例:

要求设计代码打印一个二维数组的元素:

法一:形参为数组

#include<stdio.h>
void print2(int arr[3][5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (int j = 0; j < r; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print2(arr, 3, 5);
	return 0;
}

这也是我们常用的方法;

法二:形参为指针:

#include<stdio.h>
void print2(int(*p)[5], int c, int r)
{
	int i = 0;
	for (i = 0; i < c; i++)
	{
		int j = 0;
		for (int j = 0; j < r; j++)
		{
			printf("%d ", *(*(p+i)+j));
         //也可以写为:printf("%d ",p[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print2(arr, 3, 5);
	return 0;
}

解析:

?稍作总结:
?

int arr[5];  
//arr是一个整型数组,每个元素是int型的
int* parr1[10]; 
//parr1是一个10个整型元素的指针数组
int(*parr2)[10];
 //parr2是一个数组指针,每一个元素的类型是int
int(*parr3[10])[5]; 
//parr3是一个数组,有十个元素,每个元素的类型是int(*)[5];、
//每一个元素是一个数字指针∴parr3是一个存放数组指针的数组

parr3图解:

void test1(int(*p)[5])
{ }
void test2(int(*p)[3][5])
{ }
int main()
{
	int arr[3][5];
	test1(arr);
	test2(&arr);
	return 0;
}

4.数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参?

4.3 一级指针传参?

#include<stdio.h>
void test(int* ptr)      //当形参为一个指针时
{
	//...
}
int main()
{
	int a = 10;
	int* p = &a;
	int arr[10];
	test(arr);  //数组
	test(p);    //指针
	test(&a);
	return 0;
}

4.4 二级指针传参

#include<stdio.h>
void test(char** ppc)
{
	//...
}
int main()
{
	char a = 'w';
	char* pa = &a;
	char**ppa=&pa;  
	test(ppa);     //ppa是一个二级指针
	test(&pa);
	char* arr[4];
	test(arr);     //指针数组
	return 0;
}

5.函数指针

5.1 函数指针的定义

函数指针即指向函数的指针

#include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int arr[10];
	int(*p)[10]=&arr;    //p是一个数组指针变量
	printf("%p\n", &add);
	printf("%p\n", add);   //两种表示都是函数的地址
	int(*pf)(int,int)=add;     //pf就是函数指针
 //(指向函数的返回类型)(*指针名)(指向函数的参数类型)
	return 0;
}

运行结果如下:

?表示函数地址时,函数名等同于&函数名,二者表示形式不同但意义完全相同。

同时请注意,当我们去除函数指针名就可以得到函数指针类型,即int (*) (int ,int);

5.2 函数指针的使用

#include<stdio.h>
int add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*pf)(int,int)=add;  

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

	int ret2 = add(2, 3);
	printf("%d\n", ret2);
	
	int ret3 = pf(2, 3);
	printf("%d\n", ret3);
	return 0;
}

运行结果如下:

? ? ?可以发现其实pf与*pf完全等效

一个有趣的例子:

?6.函数指针数组

函数指针数组即存放函数地址的数组,表达为:

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;
}
#include<stdio.h>
int main()
{
	int(*pf1)(int, int) = add;
	int(*pf2)(int, int) = sub;
	int(*pf3)(int, int) = mul;
	int(*pf4)(int, int) = div;
	//创建一个函数指针数组
	int(*pf[4])(int, int) = { add,sub,mul,div };
    int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 2);
		printf("%d ", ret);
	}
	return 0;
}

当函数返回类型、函数参数类型、函数参数数量都相同时,我们可以利用函数指针数组进行合并,简化代码,以简单实现计算器部分功能为例:

#include<stdio.h>
void menu()
{
	printf("*********************\n");
	printf("********1.Add********\n");
	printf("********2.Sub********\n");
	printf("********3.Mul********\n");
	printf("********4.Div********\n");
	printf("********5.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(*Arr[])(int, int) = { NULL,Add,Sub,Mul,Div };
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>");
			scanf("%d%d", &x, & y);
			ret = Arr[input](x, y);
			printf("ret=%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

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

#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;
}
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7 };
	int* arr2[5];
	//数组指针
	int(*p1)[10] = &arr1;
	int*(*p2)[5] = &arr2;
	//函数指针
	int(*pf)(int,int) = &Add;
	//函数指针数组
	int(*pfArr[4])(int,int);
	//p3是指向函数指针数组的指针
	int(*(*p3)[4])(int,int) = &pfArr;
	return 0;
}

图示:

函数调用:

#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;
}
int main()
{
	//函数指针数组
	int(*pfArr[4])(int, int) = { Add,Sub,Mul,Div };
	//p3是指向函数指针数组的指针
	int(*(*p3)[4])(int,int) = &pfArr;
	//调用函数
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = (*p3)[i](16, 4);
			printf("%d\n", ret);
	}
	return 0;
}

8.回调函数

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

以简单实现计算器部分功能为例:

常用的switch、case语句的实现方式如下:

#include<stdio.h>
void menu()
{
	printf("*********************\n");
	printf("********1.Add********\n");
	printf("********2.Sub********\n");
	printf("********3.Mul********\n");
	printf("********4.Div********\n");
	printf("********5.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;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Add(x, y);
			printf("ret=%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Sub(x, y);
			printf("ret=%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Mul(x, y);
			printf("ret=%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			ret = Div(x, y);
			printf("ret=%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

但是我们会发现在case 1至case 4语句部分代码存在大量重复,这时候我们可以分装函数,将Add,Sub,Mul,Div函数设计为回调函数,进行代码简化:

#include<stdio.h>
void menu()
{
	printf("*********************\n");
	printf("********1.Add********\n");
	printf("********2.Sub********\n");
	printf("********3.Mul********\n");
	printf("********4.Div********\n");
	printf("********5.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;
	printf("请输入两个操作数:>");
	scanf("%d%d", &x, &y);
	pf(x, y);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 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");
		}
	} while (input);
	return 0;
}

(2)qsort函数:

qsort是一个库函数,是基于快速排序算法实现的一个排序函数,可以排序任意类型的数据。

?ps:(1)比较函数要求编写者自定义是因为,并非所有数据都像整型数据一样可以直接用>或<进行排序,需要我们根据实际情况提供一个函数,实现两个函数的比较;

(2)void* 类型的指针可以接收任何类型的地址,但是该种类型的指针如果直接解引用不能确定操作的权限,故而不能直接进行解引用操做;

我们可以将bubble_sort(冒泡排序)根据qsort函数逻辑进行改进,使其基于冒泡排序可以对任意类型数据都可以进行排序:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void print_arr(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
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 num,int width,void*(*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	for (i = 0; i < num-1; i++)
	{
		int j = 0;
		for(j = 0; j <num-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 cmp_int(const void* e1, const void* e2)
//无确切类型的指针不能直接进行解引用操做
{
	//表达方式一:
	/*if (*(int*)e1 > *(int*)e2)
		return 1;
	else if (*(int*)e1 == *(int*)e2)
		return 0;
	else
		return -1;*/
		//表达方式二:
	return *(int*)e1 - *(int*)e2;
}
//冒泡排序
void test1()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//改为排序为升序
	bubble_sort(arr, sz,sizeof(arr[0]),cmp_int);
	print_arr(arr, sz);
}
//qsort函数排序整型数据
void test2()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1 };
	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;
	double score;
};
//int cmp_stu_bu_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)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
//排序结构体
void test3()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",30,88.0},{"wangwu",10,90.0}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]),cmp_stu_bu_age);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main()
{
	test1();
	printf("\n");
	test2();
	printf("\n");
	test3();
	return 0;
}

9.例题

1.一维数组:

//sizeof计算的是对象所占内存的大小
    int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a)); //4×4=16
//sizeof(数组名)表示整个数组的大小
	printf("%d\n", sizeof(a + 0)); //4 or 8
//不属于特例的数组名出现表示数组首元素地址,a+0仍表示首元素地址
//地址的大小在32位平台(×86)上是4个字节,在64位平台(×64)上是8个字节
	printf("%d\n", sizeof(*a)); //4
//sizeof(int型数据)
	printf("%d\n", sizeof(a + 1)); //4 or 8
//数组首元素地址+1表示数组第二个元素的地址
	printf("%d\n", sizeof(a[1])); //4
//数组第一个元素的大小,数组元素数据类型为int
	printf("%d\n", sizeof(&a)); //4
//&数组名表示数组的地址,地址大小只能为4或8个字节
	printf("%d\n", sizeof(*&a)); //16
//对数组的地址进行解引用得到的是整个数组,大小为4×4
	printf("%d\n", sizeof(&a + 1)); //4 or 8
//对数组的地址+1跳过了整个数组,指向数组末元素的后方,仍然是一个地址
	printf("%d\n", sizeof(&a[0]));4 or 8
//数组首元素的地址
	printf("%d\n", sizeof(&a[0] + 1)); // 4 or 8
//数组第二个元素的地址

注:数组名在一般情况下都表示数组首元素地址,但是有两个特例:

① sizeof(数组名)表示数组的大小,单位为字节;

② &数组名表示整个数组的地址;

2.字符数组

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr)); //6
//sizeof(数组名)为数组大小,单位为字节
printf("%d\n", sizeof(arr+0));//4 or 8
//数组名不属于特例,arr表示首元素地址,+0后仍为首元素地址
printf("%d\n", sizeof(*arr));//1
//对数组首元素地址解引用,得到数组首元素,char类型数据大小为1字节
printf("%d\n", sizeof(arr[1]));//1
//arr[1]表示数组第二个元素,char类型数据大小为1个字节
printf("%d\n", sizeof(&arr));//4 or 8
//&数组名为整个数组地址,整个数组地址的大小为4或8个字节
printf("%d\n", sizeof(&arr+1));//4 or 8
//整个数组的地址+1仍然得到一个地址,该地址是从数组地址开始向后跳过了整个数组产生的一个地址
printf("%d\n", sizeof(&arr[0]+1));//4 or 8
//数组第二个元素地址的大小

printf("%d\n", strlen(arr)); //大于6的随机值
//strlen函数以\0作为结束标志,arr数组中没有\0,strlen函数会继续向后找\0,f后内存空间未知
printf("%d\n", strlen(arr + 0)); //大于6的随机值
//arr+0仍然表示数组首元素地址,逻辑同上
printf("%d\n", strlen(*arr)); //本质是错误的,非法访问
//*arr表示数组首元素,即'a',其ASCII码值为97,没有传递给strlen一个地址,本质上是错误的代码
printf("%d\n", strlen(arr[1])); //本质是错误的,非法访问
//同上,将b元素的ASCII码值传递给了strlen
printf("%d\n", strlen(&arr)); //大于6的随机值
//将整个数组的地址传递给strlen函数,数组中不含有\0
printf("%d\n", strlen(&arr + 1)); //随机值
同上,从数组最后一个元素结束开始向后寻找\0
printf("%d\n", strlen(&arr[0] + 1)); //大于5的随机值
//&arr[0]+1表示数组第二个元素从a元素开始向后寻找\0

3.字符串

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));  //7
//字符串默认结尾为\0,而sizeof(数组名)计算的是整个数组的大小,
printf("%d\n", sizeof(arr+0)); //4 or 8
//数组名表示数组首元素地址,即sizeof(地址)
printf("%d\n", sizeof(*arr)); //1
//*arr表示数组首元素,char类型数据大小为1个字节
printf("%d\n", sizeof(arr[1])); //1
//数组第二个元素的大小
printf("%d\n", sizeof(&arr));//4 or 8
//数组的地址仍然为一个地址
printf("%d\n", sizeof(&arr+1)); //4 or 8
//数组的地址+1后仍然为一个地址
printf("%d\n", sizeof(&arr[0]+1));//4 or 8
//&arr[0]+1表示数组第二个元素的地址

printf("%d\n", strlen(arr)); //6
//strlen函数统计的是\0前的字符个数
printf("%d\n", strlen(arr+0)); //6
printf("%d\n", strlen(*arr)); //写法错误
//*arr表示数组第一个元素,会造成非法访问
printf("%d\n", strlen(arr[1])); //写法错误
//同上
printf("%d\n", strlen(&arr)); //6
//strlen函数只要接收一个地址就会从该地址开始向后寻找\0并统计数目
printf("%d\n", strlen(&arr+1)); //随机值
//数组地址+1就是将\0后的地址交给了strlen函数,后面内存未知
printf("%d\n", strlen(&arr[0]+1)); //5
//&arr[0]+1表示数组第二个元素,至\0有5个元素

关于指针:?

char* p = "abcdef";
printf("%d\n", sizeof(p)); //4 or 8
//p存放的是a的地址,此处计算的是指针变量的大小
printf("%d\n", sizeof(p+1)); // 4 or 8
//p+1表示的是b的地址
printf("%d\n", sizeof(*p)); // 1
//*p表示的是字符a
printf("%d\n", sizeof(p[0])); //1
//p[0]等价于*(p+0)等价于*p等价于字符a
printf("%d\n", sizeof(&p)); // 4 or 8
//p表示的是a的地址,&p存放p的地址,仍然为一个地址
printf("%d\n", sizeof(&p+1)); // 4 or 8
//&p+1仍然是一个地址
printf("%d\n", sizeof(&p[0]+1)); //4 or 8 
//&p[0]表示字符a的地址,+1后表示字符b的地址
printf("%d\n", strlen(p)); //6
//p等同于a的地址
printf("%d\n", strlen(p+1)); //5
//p+1等同于b的地址
printf("%d\n", strlen(*p)); // 错误
//将a的值传递给了strlen
printf("%d\n", strlen(p[0])); //错误
//同上
printf("%d\n", strlen(&p)); //随机值
//存放a的地址的地址后的内存未知
printf("%d\n", strlen(&p+1)); //随机值
//同上
printf("%d\n", strlen(&p[0]+1)); //5
//&p[0]+1等同于字符b

4.二维数组

int a[3][4] = {0};
printf("%d\n",sizeof(a));//48
//sizeof(数组名)表示整个数组的大小,该数组共12个int型数据
printf("%d\n",sizeof(a[0][0]));//4
//a[0][0]表示数组第一行第一列元素大小
printf("%d\n",sizeof(a[0]));//16
//a[0]表示二维数组第一行元素,共4个int型数据,共占16个字节
printf("%d\n",sizeof(a[0]+1));//4 or 8
//a[0]表示第一行元素的数组名,也是a[0][0]的地址,+1后表示第一行第二个元素的地址
printf("%d\n",sizeof(*(a[0]+1)));//4
//a[0]+1表示a[0][0]的地址,解引用得到a[0][1]
printf("%d\n",sizeof(a+1));// 4 or 8
//a表示数组第一行元素的地址,+1跳过第一行数组,表示数组第二行元素的地址
printf("%d\n",sizeof(*(a+1)));//16
//a+1表示数组第二行元素的地址,解引用得到第二行元素,共4个整型数据
printf("%d\n",sizeof(&a[0]+1));// 4 or 8
//第一行数组的地址+1得到第二行数组的地址
printf("%d\n",sizeof(*(&a[0]+1)));//16
//解引用得到第二行数组
printf("%d\n",sizeof(*a));//16
//a表示第一行元素的地址,解引用得到数组第一行元素共4个int型数据
printf("%d\n",sizeof(a[3]));//16
//a[3]表示数组第四行的元素,内存不会真的访问,识别类型后直接得到大小,为16个字节

ps:

(1)sizeof是一个单目操作符,计算的是对象所占内存的大小,单位是字节,返回类型是size_t,它不在乎内存中存放的是什么,只在乎内存的大小;

(2)strlen是一个库函数,专用于计算字符串的长度,从给定的地址向后访问字符,统计该地址到\0之前的字符数;

(3)在理解二维数组时,可以先将二维数组想象为一维数组,将二维数组的每一行看作一个元素,比如二维数组arr[3][4],arr[0]就表示第二行元素的数组名。

10.笔试题

(1)

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
    //&a取出挣个数组的地址,类型为数组指针,int(*)[5],故而强制类型转换为int*
    printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

输出结果为2,5;

(2)

struct Test
{
 int Num;
 char* pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}* p;
//已知,结构体Test类型的变量大小是20个字节
int main()
{
 p=(struct Test*) 0x100000;
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
//将p转化为无符号长整型,+1即+一个字节
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0; }

输出结果为00100014 00100001?00100004;

%p打印的是地址,十六进制需要展现32位,即4个字节;

(3)

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

输出结果4 2000000

图示:

?(4)

#include <stdio.h>
int main()
{
 ? ?int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//注意区别与int a[3][2]={{0,1},{2,3},{4,5}};的区别
//{ (0, 1), (2, 3), (4, 5) }是逗号表达式,相当于{1,3,5}
//这个二维数组是:{{1,3},{5,0},{0,0}}
 ? ?int *p;
 ? ?p = a[0];
 ? ?printf( "%d", p[0]);
    return 0;
 }

?输出结果为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; 
}

?输出结果为fffffc -4;

图示:

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

?输出结果为 10,5

注解:在int *ptr2 = (int *)(*(aa + 1));中,aa表示数组第一行元素的地址,+1后表示数组第二行元素的地址,解引用得到了数组第二行元素,也就是数组第二行元素的数组名,同时也表示第二行数组首元素6的地址,再进行ptr2-1得到元素5的地址,解引用得到元素5;

(7)

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

输出结果为

注解:a是一个指针数组,其元素为三个字符串首字符的地址。再将a的首元素的地址赋值给二级指针变量pa,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; }

图示:

?注解:① ++cpp指向c+2,解引用得到c+2,再解引用得到其指向的内容P的地址,%s形式打印得到字符串POINT;

② ++前置会改变原本的值,第二句输出建立在第一次输出的基础上,++cpp指向c+1,解引用得到c+1,再--得到c,此步骤将原本c+1指向的空间改为c指向的空间,再解引用得到E的地址,+3指向E,打印结果为ER;

③ cpp经过以上两个步骤已经指向c+1,cpp[-2]等价于*(cpp-2),即第三句输出的是**(cpp-2)+3,cpp-2指向c=3,解引用得到c+3,再解引用得到F的地址,+3得到S的地址,打印结果为ST;

④ cpp[-1][即*(cpp-1),cpp[-1][-1]等价于*(*(cpp-1)-1),即第三句输出的是*(*(cpp-1)-1)+1,注意上一句输出结果并未改变cpp的指向,cpp仍然指向c+1,-1指向c+2,解引用得到c2,再-1得到c+1,指向N的地址,再解引用得到N,再+1得到E,打印结果为EW;

||终

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-21 00:08:17  更:2022-09-21 00:09:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:00:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码