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语言进阶学习笔记】二、指针进阶 -> 正文阅读

[C++知识库]【C语言进阶学习笔记】二、指针进阶

  • 指针可以理解为一个变量,里面存放着地址,可以通过解引用来访问该地址上存储的数据。
  • 指针是有类型的,指针的类型决定着一个指针在+1或者-1时跳过多少个字节;和指针解引用时能访问的内存的大小。

1、字符指针

字符指针中存储着一个地址,该地址的内存块上存储着一个字符;字符指针用char*表示。
一般例如:

int main(){
    char ch = 'x';
    char *ptr = &ch;
    printf("%c\n", *ptr);
    return 0;
}

在这里插入图片描述
字符指针除了指向字符,还有另一个用法–指向字符串。通常 定义字符串char arr[] = "xupeng",其中arr存储的是字符串首个字符的地址然后寻找直至遇到'\0'表示一个完整字符串,其关键在字符串首个字符的地址上,故也可以使用字符指针来指向字符串第一个字符的地址,如下:

int main() {
    char *ptr = "xu peng";
    printf("%s\n", ptr);
    return 0;
}

在这里插入图片描述
通过vs监视窗口可以看到,char *ptr = "xu peng"并不是把xu pengf放进变量ptr里,因为ptr是个指针类型只有4/8字节,压根放不下一整个字符串;可见是把首字符x的地址存进了ptr中。但char arr[] = "xu peng"虽然数组名arr代表的也是首字符x的地址,但是在内存中的存储形式大不一样,如下代码可分析:

int main() {
	char str1[] = "hello xp";
	char str2[] = "hello xp";
	const char *ptr1 = "hello xp";
	const char* ptr2 = "hello xp";

	if (str1 == str2) {
		printf("str1 and str2 are same\n");
	}
	else {
		printf("str1 and str2 are not same\n");
	}
	if (ptr1 == ptr2) {
		printf("ptr1 and ptr2 are same\n");
	}
	else {
		printf("ptr1 and ptr2 are not same\n");
	}
	return 0;
}

在这里插入图片描述
可见数组名str1与str2表示的不是同一片空间,而指针ptr1和ptr2指向同一片空间,其在内存中的状态如下:
在这里插入图片描述
数组是是实实在在地存下了一个字符串,用数组名表示首元素地址;而指针只是个变量,指向的同一片内存(存储的是同一片地址的首个字节的地址)

2、指针数组

指针数组的本质还是个数组,只不过数组里的每个元素是个指针,也就是每个元素是内存的地址。

有如下代码:

int main() {
    int arr1[] = {1,2,3};
    int arr2[] = {2,3,4};
    int arr3[] = {3,4,5};
    int *arr[3] = {arr1, arr2, arr3};
    return 0;
}

数组名arr1arr2arr3表示的是各自数组首元素的地址,因为数组元素是整型,故arr1arr2arr3表示的是整形的地址,其也就叫做整型指针,也就是int *类型。故创建数组arr,里有三个元素,每个元素的类型是int *,故arr数组元素的类型正好跟arr1arr2arr3的类型一致,故可以int *arr[3] = {arr1, arr2, arr3}
在这里插入图片描述

3、数组指针

数组指针本质是指针,也就是个存着一种地址变量,这种地址是数组的地址。(重点)跟数组首元素地址不同,数组首元素要么是int类型要么是char,所以其地址是整型指针或者字符指针,这跟数组的指针有本质区别,要区分开。

int main(){
    int arr[4] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}

在这里插入图片描述
可以看到arr&arr的值是一样的,因为地址肯定都只是4字节,而计算机如何表示一个数组的呢?最方便的也就是指向首元素,故首元素arr和数组地址&arr的值是一样,但是两者意义完全不一样。打个比方,两个人A和B,A一次只能跨1个台阶,而B一次能跨4个台阶;虽然A和B的跨度(体量)不一样,但是他们起脚的地方肯定都是第一个台阶处,这就是arr&arr一样的原因;但是跨一次后A和B所处的位置就完全不一样了。如下代码所示:

int main(){
    int arr[4] = {0};
    printf("%p\n", arr); // A的起脚处。
    printf("%p\n", &arr); //B的起脚处。
        
    printf("%p\n", arr+1); //A跨一步后的位置。
	printf("%p\n", &arr+1); //B跨一步后的位置。
    
    return 0;
}

在这里插入图片描述
可见A和B起脚处相等,但是跨一步后A只走了4字节(一个int型的大小),而B走了16字节(4个int型大小,arr数组中一共有4个int型元素,可知B跨过了一个数组)。

数组指针的表示方法

int (*ptr)[10] ;其中ptr先与*结合表示其是一个指针,[10]表示ptr指向一个有10个元素的数组,int表示每个数组的类型的int型。

数组名一般是首元素地址,但是有两个例外:

1、sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小

2、&数组名,数组名表示整个数组,取出的是整个数组的地址

巩固

int arr[5]; 
//arr是变量名,与[5]结合表示arr是一个有5个元素的数组,每个数组的类型是int,其是整型数组

int *ptr1[10]; 
//优先级缘故,ptr1首先与[]结合,表示ptr1是一个数组,有10个元素,每个元素的类型是int*,其是指针数组

int (*ptr2)[10];
//ptr2首先与*结合,表示ptr2是个指针类型的变量,[10]表示ptr2指向一个数组,该数组有10个元素,每个元素是int类型,其是数组指针

int (*ptr3[10])[5];
//ptr3首先与[10]结合表示ptr3是个有10个元素的数组,int (*)[5]表示数组中每个元素的类型是一个数组指针,其是一个存储数组指针的数组

4、数组传参和指针传参

一维数组传参

void test(int arr[10]){}

void test(int arr[]){}

void test(int *arr[]){}

int main() {
    int arr[10] = {0};
    test(arr);
    return 0;
}
  1. void test(int arr[10]):在实参中传入数组,形参用相同类型数组接收,完全一致。
  2. void test(int arr[]):实参传入数组,形参也用同类型数组接收,与上面不同的是少了参数10,形参中一维数组大小的参数可以不传,因为传了形参也不会立即开辟相应大小的空间。
  3. void test(int *arr[]):实参传入数组,但换个角度理解其也是数组名,数组名表示数组首元素地址,数组首元素是int类型;形参中,*表示arr是个指针用来接收地址,前面的int表示其接收的地址所存储元素的类型(指针指向的类型)是int类型,这也与数组首元素的类型一致,这样传参符合。

二维数组传参

// 正确的
void test(int arr[3][5]);
void test(int arr[][5]);
void test(int (*arr)[5]);

// 错误的
void test(int arr[][]);
void test(int *arr);
void test(int *arr[5]);
void test(int **arr);



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

先解释正确的:

  1. void test(int arr[3][5])实参传数组,形参用同类型的数组接收,完全一致,没问题。
  2. void test(int arr[][5])实参传数组,形参用同类型的数组接收,但是只能省去第一个[]中的数字,第二个[]中的数字不能省,也必须与传入数组的列数一致。
  3. void test(int (*arr)[5])实参另一个角度看传的是数组名,数组名表示数组首元素地址,二维数组的首元素是第一行的一维数组(是数组,是数组,是一整个数组的概念),故arr表示一维数组的地址(不是,不是,不是首元素地址,是数组概念级别的地址)。所以,形参要接收数组地址,*与arr结合表示arr是个指针(存储地址),[5]( *arr )一起表示指针指向的是一个数组,int表示数组的元素是int类型;而实参传过来的地址也刚好是有5个int类型元素的数组的地址,故一致,可以这么表示。

再解释不正确的:

  1. void test(int arr[][])规定,问就是规定!!!规定后一个表示有多少行的参数不能省略,对于一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素。
  2. void test(int *arr)形参表示arr是一个指针,指向int类型的元素‘而传过来的不是一维数组首元素地址,而是一个数组的地址,传入和接受的不一致。
  3. void test(int *arr[5]) *的优先级比[ ]低,故arr首先与[ ]结合表示arr是个数组;实参传的是一维数组地址,形参是个数组;数组能和数组地址是一个概念?
  4. void test(int **arr)*与arr结合表示arr是一个指针,存放地址;int *表示arr指针指向的元素的类型是int *,而实参传入的是数组的地址,其应该指向一个数组,不一致,故不可以。

一级指针传参

void test(int *ptr){
   return;
}

int main(){
    int arr[10] = {1,2,3,4,5};
    int *p = arr;
   	test(p);
    return 0;
}

实参传过来的是一级指针,形参只能同类型一级指针接收。

二级指针传参

#include "stdio.h"

void test(int* *ptr){}

int main(){
  int n = 10;
  int *p = &n;
  int *PP = &p;
  test(pp);
	return 0;
}

在这里插入图片描述

5、函数指针

指向一个函数的指针叫函数指针,其形式为void (*ptr)(int, int)ptr是变量名,与*结合表示其是个指针,存放着地址,再与(int, int)结合,表示其指向一个函数,void是指向的这个函数返回值类型。
1、首先我们来看函数的地址的特别之处
在这里插入图片描述
可以看到,对于函数的地址,函数名&函数名是等价的。
2、函数指针的解引用的特别之处

int (*ptr)(int, int) = &test;

int ret1 = ptr(1, 2);
int ret2 = (*ptr)(1, 2);

以上ret1ret2的对于函数指针的使用方式是等价的。函数指针调用时(*p)中的*只是一个摆设,为了更加容易理解而已,可以不写。

3、函数指针的typedef

typedef void(*p_func)(int, int)表示将函数指针类型void (*)(int, int)定义为p_func;如下所示:

#include "stdio.h"
typedef void(*p_func)(int, int);

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

int main(){
  int (*p1)(int, int) = test;
  p_func p2 = test;
  return 0;
}

上面的代码p1p2是等价的。

6、函数指针数组

函数指针数组本质是个数组,是个存放着同类型的函数指针的数组;其形式如下:

int (*ptr[10])(int, int)ptr首先与[10]结合表示这是个数组,剔除数组ptr[10]剩下的int (*)(int, int)表示数组元素的类型为函数指针类型。

函数指针数组的一个用途的----转移表

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

指向函数指针数组的指针本质是个指针,所以形式里一定要确保*与变量名先结合;其具体形式如下:

void (*(*ptr)[10])(int, int),首先*ptr表示ptr是个指针变量名,然后 ( *ptr )[10]结合表示其指向一个数组,剔除数组,剩下的void ( * )(int, int)表示数组的元素类型为函数指针类型。

8、回调函数

假设有函数A和函数B,在函数B中需要使用函数A的功能,不直接在函数B中使用函数A,而是将函数A的地址作为参数传递给函数B,在函数B中通过函数指针接收函数A的地址,来使用函数指针调用函数A进行运算,这样的形式称为回调函数机制。下面给出一个例子:

#include <stdio.h>

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

int sub(int x, int y) {
	return x - y;
}

int Mul(int x, int y) {
	return x * y;
}

int Div(int x, int y) {
	return x / y;
}

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

int calcu(int (*ptr)(int, int)) {
	int x = 0;
	int y = 0;
	printf("请输入参与运算的两个数:\n");
	scanf("%d", &x);
	scanf("%d", &y);
	return ptr(x, y);
}

int main() {
	int ret = 0;
	int input = 0;
	do {
		menu();
		printf("请输入选项:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			ret = calcu(Add);
			printf("%d\n", ret);
			break;
		case 2:
			ret = calcu(sub);
			printf("%d\n", ret);
			break;
		case 3:
			ret = calcu(Mul);
			printf("%d\n", ret);
			break;
		case 4:
			ret = calcu(Div);
			printf("%d\n", ret);
			break;
		case 0:
			break;
		default:
			break;
		}
	} while (input);
}

通过将加减乘除四个函数,以函数指针的形式在calcu中进行调用。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-16 11:33:49  更:2021-08-16 11:34:10 
 
开发: 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年12日历 -2024/12/26 15:50:24-

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