【C的葵花宝典进阶篇】之指针进阶(一)

news2024/9/24 23:29:20

【C语言进阶篇】之指针进阶(一)

  • 1. 字符指针
  • 2. 指针数组
    • 2.1 整形指针数组
    • 2.2 用指针数组模拟二维数组
  • 3. 数组指针
    • 3.1 数组指针的表示方法
    • 3.2 深度剖析&数组名和数组名
    • 3.3 数组指针的使用
      • 3.3.1 在同一函数内直接将数组的地址赋给数组指针
      • 3.3.2 数组指针在二维数组传参上的应用
        • 3.3.2.1 二维数组传参使用二维数组
        • 3.3.2.2 二维数组传参使用数组指针
  • 4. 数组传参和指针传参
    • 4.1 一维数组传参
    • 4.2 二维数组传参
    • 4.3 一级指针传参
    • 4.4 二级指针传参
  • 5. 函数指针
    • 5.1 函数的地址
    • 5.2 函数指针的表示方法
    • 5.3 两行有趣的代码
      • 5.3.1 将0强制转换为函数指针类型然后调用
      • 5.3.2 数组指针、函数指针等特殊变量类型typedef的使用
      • 5.3.3 函数指针类型的函数的声明,并使用别名typedef化简

❤️博客主页: 小镇敲码人
🍏 欢迎关注:👍点赞 👂🏽留言 😍收藏
🌞回来4天了,加油!!!🍎🍎🍎
💗当你的能力匹配不上你的梦想,当你需要实现的目标匹配不上你的圈子的时候,你就会出现错位。当我们的能力还匹配不上我们的梦想时,我们就需要沉淀学习。✡️✡️✡️

1. 字符指针

字符指针,就是储存字符变量地址的指针,这是一种指针的类型。

  • 通常我们字符指针有如下两种用途
  1. 通过字符指针间接改变字符的值
    int main()
    {
      char ch = 'c';
     // ch = 'a';//直接通过变量赋值更改ch的值
      char *p = &ch;//将字符ch的地址存入p中
      //*p = 'a';//通过对p里面的地址解引用找到ch,间接的改变ch的值
    }
  1. 借助字符指针对字符串进行储存和打印
 int main()
 {
   const char* ptr = "abcdef";
   printf("%s\n",ptr);
   return 0;
 }
  • const修饰字符串表示"abcdef"不可修改,为常量字符串
  • 字符指针变量ptr只储存了字符'a'的地址。 (可类比字符数组理解,实际上字符串是字符数组的一种形式,在C语言中,字符串是由字符数组表示的,以空字符(‘\0’)作为字符串的结束标志,又因为数组名表示首元素地址,所以字符指针也只储存了首元素的地址)

2. 指针数组

      指针数组是由指针组成的数组。在C和C++等编程语言中,指针是一个变量,用于存储内存地址。指针数组是一个数组,其每个元素都是指针类型。
    int *arr1[10];    整形指针数组
    char *arr2[10];  一级字符指针的数组
    char **arr3[10]; 二级字符指针的数组

2.1 整形指针数组

int main()
{
    int a = 0;
        int b = 1;
    int c = 2;
    int* arr[] = { &a,&b,&c };
    for (int i = 0; i < 3; i++)
    {
        printf("%d ", *arr[i]);
    }
}

2.2 用指针数组模拟二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };//arr1-int*
	int arr2[] = { 2,3,4,5,6 };//arr2-int*
	int arr3[] = { 3,4,5,6,7 };//arr3-int*
   //指针数组
	int* p[] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
		  //这两种方式打印都可以,两者择一
			printf("%d ", *(*(p + i) + j));
            printf("%d ",p[i][j]);
		}
		printf("\n");
	}
}

3. 数组指针

我们可以通过类比来理解

整型指针:int * a;能够指向整形数据的指针
浮点型指针:float* b;能够指向浮点型数据的指针。
那么数组指针应该是:能够指向数组的指针。

3.1 数组指针的表示方法

数组的类型表示是什么?通过简单的类比可以知道。

  1. int a = 3;类型为除变量名以外的部分int就是整形的类型表示形式。
  2. char b = 'a';类型为除变量名以外的部分char就是字符类型的的表示形式。
  3. int arr[10] = {0};类型为除变量名以外的部分int [10]就是这个数组的变量类型表示形式。
  4. 而数组指针的类型是指针,指针的类型是数组,通过前面的学习我们知道,变量类型+变量名为一个变量,所以一个数组指针变量就是,指向数组的指针变量,int [10] *p = &arr;,看起来似乎是这样,但是通过下面符号的优先级我们可以知道[]的优先级要比*号高,所以我们要给*p加上括号,让其成为指针,int [10] (*p) = &arr,那这样是否正确呢?还是不对,放进vs编译器会报错,数组的类型比较特殊,应该是这样int (*p) [10] = &arr;才正确。

3.2 深度剖析&数组名和数组名

对于下面的数组

 int arr[10];
  • arr&arr分别是什么呢?
    对于arr,我们知道它是一个数组名,数组名表示首元素的地址,
    那&arr是什么呢,我们来看下面一段代码:
int main()
{
	int arr[10] = { 0 };
	printf("arr       = %p\n", arr);
	printf("arr+1     = %p\n", arr+1);

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

运行结果如下:
在这里插入图片描述
可以看到数组名和&数组名打印的地址和首元素打印的地址是一样的,这是不是意味着它们是一样的呢?很明显通过代码我们也可以发现,它们并不是相同的,因为&arr+1打印的地址与arr+1打印的地址相差了40个字节,而arr+1打印的地址和首元素&arr+1的仍然一样,进一步说明,arr就表示首元素地址。

  • 实际上:&arr表示的是整个数组的地址,它的类型是int (*)[10],是一种数组指针类型。
                   而arr代表的是数组首元素的地址,它的类型是int *
                   数组的地址加1,跳过整个数组的大小,所以&arr+1与&arr的差值就是 10 ∗ 4 = 40 10*4 =40 104=40个字节。

3.3 数组指针的使用

3.3.1 在同一函数内直接将数组的地址赋给数组指针

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int[10] *p = &arr;err
	int (*p)[10] = &arr;
	for (int i = 0; i < 10; i++)
	{
	    //两种打印方式择一
	    printf("%d ",*((*p)+i));
		printf("%d ", (*p)[i]);
	}
	//int* p2 = &arr;//err
	return 0;
}

  • 可以看到,这样做简直就是多此一举,因为想打印一维数组,我们直接打印就行了,使用数组指针,反倒是比较麻烦。

3.3.2 数组指针在二维数组传参上的应用

3.3.2.1 二维数组传参使用二维数组

#include<stdio.h>
void Print(int arr[][5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; 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 };
	Print(arr, 3, 5);
	return 0;
}

3.3.2.2 二维数组传参使用数组指针

#include<stdio.h>
Print(int(*p)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; 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 };
	//arr表示数组名,数组名表示首元素的地址,也就是那一行的地址
	Print(arr, 3, 5);
	return 0;
}
  • 因为二维数组的数组名代表首元素的地址,首元素地址就是那一行的地址,就等价于一维数组的地址(&一维数组数组名),故又称二维数组为一维数组的数组,所以可以用数组指针来接收。

4. 数组传参和指针传参

4.1 一维数组传参

  • 有以下主要两种方式
  1. 传递数组名,用一级指针的方式去接收,通过地址访问
  2. 传递数组的副本
#include<stdio.h>
void test1(int arr[])
{}
void test1(int arr[10])
{}
void test1(int* arr)
{}
void test2(int* arr[20])
{}
void test2(int** arr)
{}
int main()
{
	int arr1[10] = { 0 };
	int* arr2[20] = { 20 };
	test1(arr1);
	test2(arr2);
	return 0;
}

4.2 二维数组传参

  • 正确的方法已经在“3.3.2 数组指针在二维数组传参上的应用“版块给出,下面就几种错误的传参方法做一下阐述。
#include<stdio.h>
void test(int arr[3][5]){}
void test(int arr[][])//err,二维数组传参,如果是传递数组的副本,需要给出一行有多少数字,因为二维数组其实是一维数组的扩展,所以需要提供一行有多少元素,来正确进行内存访问
{}
void test (int arr[][5]){}
void test(int *arr)//err,传的是一行的地址,指针的类型是数组,int(*)[5]。
{}
void test(int *arr[5])//err,传的是第一行的地址,用指针数组接收不对,因为传的是一行的地址。
{}
void  test(int (*arr)[5]){}
void test(int **arr)//err,传的是数组的地址(第一行的地址),不是一级指针的地址,不能用二级指针接收。
{}
int main()
{
	int arr[3][5] = { 0 };
	test(&arr);
	return 0;
}
  • 注意:传二维数组的副本必须给定一行有多少个数,在C语言中,二维数组在内存中以连续的块存储,可以看作是一维数组的扩展。当传递二维数组作为参数时,需要提供一行有多少个元素的信息,以便在函数内部正确地进行内存访问。
    二维数组在内存中按行主序(row-major order)存储。也就是说,二维数组的每一行依次存储在内存中,并且相邻的元素在内存中也是相邻的。通过提供一行有多少个元素的信息,可以根据内存布局准确地计算出每个元素的地址,从而进行正确的访问。
  • 二维数组不能用二级指针来接收,因为二级指针是指向一级指针的,很明显,二维数组的数组名的类型是第一行即数组的地址应该用这个int (*)[5]即用数组指针接收。
  • 二维数组也不能用指针数组接收,类型不匹配。

4.3 一级指针传参

当函数形参为一级指针时,实参可以是同类型一维数组数组名、同类型变量的地址、同类型指针。

  • 请看如下代码加深理解:
#incldue<stdio.h>
void print(int* p, int sz)
{
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i));
        printf("%d ", p[i]);
    }
}
int main()
{
    int arr[5] = { 0,1,2,3,4 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    print(arr, sz);
}
#include<stdio.h>
void test(char *p)
{}
int main()
{
  char ch = 'a';
  char *str = "abcdef";
  char* ptr = &ch; 
  test(str);
  test(&ch);
  test(ptr);
  return 0;
}

4.4 二级指针传参

当函数形参为二级指针时,实参可以是一级指针的地址、二级指针。

  • 请看下面代码,加深理解:
#incldue<stdio.h>
void test(int **ptr)
{
  printf("num = %d\n",**ptr);
}
int main()
{
  int a = 3;
  int* p = &a;
  int **pp = &p;
  test(pp);
  test(&p);
  return 0;
}

5. 函数指针

函数指针和数组指针、字符指针一样都是指针,只不过指针所指向的对象的类型不同,其中函数指针与数组指针最为相似。

5.1 函数的地址

我们先看下面代码,函数名的地址,类比数组的地址

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

运行的结果:
在这里插入图片描述
        可以看见输出的是一段地址,这两个地址都是函数Add的地址。既然函数的地址我们知道了,就可以用指针去保存它,我们只需要知道指针指向的对象的类型就行了。

5.2 函数指针的表示方法

  • *号代表变量是一个指针变量,函数指针所指向的类型就是函数声明去掉变量名和分号后保留的部分,包括返回值和参数的类型,与数组指针相似,函数指针的(*变量名)也要放在函数名的位置,而数组指针是放在数组名的位置。

请看如下代码:

void test()
{
  printf("i love programming!\n");
}
//除函数名以外的部分,就是的类型
void (*pf)();//函数指针,指向一个返回值为void,无参数的函数。
void* pf();//表示返回值类型为void*,无参数的函数。。
#incldue<stdio.h>
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;//数组指针
	//int (*)[10]是数组指针类型
	int(*pf) (int, int) = &Add;
	//int (*)(int,int)是函数指针类型
	return 0;
}

再给出一组代码帮助理解:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
     //&Add和Add都可以
	int (*pf)(int, int) = &Add;
	int (*pf)(int, int) = Add;
	
	int r = Add(3, 5);
	printf("%d\n", r);

	int m = pf(3, 5);
	printf("%d\n", m);
    return 0;
}

运行结果如下:在这里插入图片描述

  • 函数指针与数组指针有相似性,可以对比着去理解。

5.3 两行有趣的代码

5.3.1 将0强制转换为函数指针类型然后调用

void (*p)() -p是函数指针
void (*)()  -是函数指针类型 

(*  (   ( void(*)() )0 ) )();
//(((void(*)())0)是将0强制转换为函数至真
//(*(函数指针))是将函数指针解引用
//然后不传参数调用(*(函数指针))(); 

在这里插入图片描述

5.3.2 数组指针、函数指针等特殊变量类型typedef的使用

#include<stdio.h>
typedef int* ptr_t;//一级指针别名
typedef unsigned int uint;//无符号整形的别名

typedef int(*parr_t)[10];//数组指针的别名
typedef int (*pf_t)(int, int);//函数指针的别名
int main()
{
	ptr_t p1;
	uint u2;
	parr_t p2;
	pf_t p3;
}
  • 函数指针与数组指针别名的创建与其变量的创建类似,可类比理解。

5.3.3 函数指针类型的函数的声明,并使用别名typedef化简

请看如下代码:

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

解析:
在这里插入图片描述
          我们可以观察到signal函数的返回类型和其中一个参数是相同的都是函数指针类型,下面我们利用别名typedef来使这段代码看起来更加易懂:

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

          CSDN上的别名没有高亮,但VS2019里面是有高亮的,定义函数后,你会发现编译器是不会报错,说明这样是可行的。
在这里插入图片描述

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

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

相关文章

Flink提交作业时报错Cannot support file system for ‘hdfs‘ via Hadoop

Flink整合HDFS提交作业时报错 分享一个Flink提交作业时的报错&#xff0c;由于我用的是1.12版本的Flink&#xff0c;对于Hadoop3.0新特性是没有支持的&#xff0c;我的Hadoop版本是3.3.1。然后flink-shaded-hadoop-3-uber jar包是没有的&#xff0c;需要手动添加。 报错 Caus…

6、选中tiles的模型并高亮

本节演示选中3dtiles的模型&#xff0c;选中模型与ceisum基本一致。通过鼠标左键单击模型并将选中的模型进行高亮。使用上一节的示例&#xff0c;再添加鼠标左键单击事件用于选中模型。 1、修改Handler类&#xff0c;添加静态函数onLeftClick&#xff0c;参数与右键单击函数一…

ubuntu无法定位软件包 tfpd-hpa

安装sudo apt-get install tftp-hpa tftpd-hpa 提示无法定位软件包tfpd-hpa 搜索一下解决方法是需要sudo apt-get update之后才可 结果sudo apt-get update提示索引文件下载失败 按照网上的解决办法更改/etc/apt/sources.list里面的下载源问题依旧&#xff0c;然后又把source…

怎么把word转换成只有一页的长页PDF?

来百度APP畅享高清图片 要将Word文档打印成一长页的PDF格式&#xff0c;我们得先知道word转PDF的工作原理。word转pdf其实就是将word打印出来&#xff0c;就是跟你用物理打印机打印的原理是差不多的&#xff0c;所不同的是&#xff0c;PDF虚拟打印的原理是利用虚拟打印机驱动程…

新手入门深度学习 | 6-2:AlexNet(2012)详解

&#x1f517; 运行环境&#xff1a;python3&#x1f6a9; 作者&#xff1a;K同学啊&#x1f947; 精选专栏&#xff1a;《深度学习100例》&#x1f525; 推荐专栏&#xff1a;《新手入门深度学习》&#x1f4da; 选自专栏&#xff1a;《Matplotlib教程》&#x1f9ff; 优秀专栏…

oracle新建库(表空间)表

文章目录 前言一、sqlplus登录二、表空间1.新建表空间2. 查看表空间3. 查看表空间和对应数据文件4.表空间增加数据文件5.删除单个数据文件&#xff08;只有一个默认的会删除失败&#xff09;6.删除表空间及数据文件(慎用) 三、创建新用户并指定表空间1.去掉前缀2.新建用户&…

C语言是一种通用的、面向过程的编程语言

具有以下特点&#xff1a; 简洁而高效&#xff1a;C语言的语法相对简单&#xff0c;同时提供了丰富的编程结构和功能&#xff0c;使得开发者能够用较少的代码实现复杂的任务。C语言的执行效率高&#xff0c;能够直接操作计算机硬件&#xff0c;提供了底层的控制和优化能力。 可…

时间序列预测 | Matlab基于极限梯度提升树XGBoost时间序列预测,XGBoost时间序列预测模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列预测 | Matlab基于极限梯度提升树XGBoost时间序列预测,XGBoost时间序列预测模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码

后端开发常见技术场景

文章目录 1、单点登录这块怎么实现的1.1 概述1.2 JWT解决单点登录1.3 回答要点 2、权限认证是如何实现的2.1 概述2.2 RBAC权限模型2.3 回答要点 3、上传数据的安全性你们怎么控制&#xff1f;3.1 概述3.2 对称加密3.3 非对称加密3.4 回答要点 4、你负责项目的时候遇到了哪些比较…

【Ubuntu学习MySQL——导出数据报错ERROR1290】

将数据表 runoob_tbl 数据导出到 /tmp/runoob.txt 文件中出错&#xff1a; 这是因为mysql有个安全变量限制。可以通过以下语句查看设置的路径&#xff0c;然后将要导入到的文件名称改到此路径下即可。

现场工程师宝典-流式处理的异常现象以及提高吞吐能力的常见优化策略

流式处理区别于按包处理&#xff0c;指的是对处理者而言&#xff0c;面对的是逻辑上无头无尾的数据流。因此&#xff0c;在提取数据流中的包时&#xff0c;就需要遵循其内在的格式&#xff0c;进行头部捕获、提取、校验。然而&#xff0c;如果不考虑TCP等流式数据的异常情况&am…

视频怎么做成二维码?一招轻松制作二维码

怎么把视频做成二维码&#xff1f;现在用二维码来做载体存储视频&#xff0c;这种方法能够有效的减少内存占用&#xff0c;可以将视频储存在云端&#xff0c;他人只需要扫码就能够查看视频。下面来教大家一招关于视频二维码制作&#xff08;音视频二维码制作-一键免费生成音视频…

Kafka传输数据到Spark Streaming通过编写程序java、scala程序实现操作

一、案例说明 现有一电商网站数据文件&#xff0c;名为buyer_favorite1&#xff0c;记录了用户对商品的收藏数据&#xff0c;数据以“\t”键分割&#xff0c;数据内容及数据格式如下&#xff1a; 二、前置准备工作 项目环境说明 Linux Ubuntu 16.04jdk-7u75-linux-x64scal…

(LFPAK56)BUK7Y7R0-40HX 40V、N 通道BUK9Y6R5-40HX表面贴装汽车用MOSFET器件

汽车用MOSFET将低压超级结技术与先进的封装设计相结合&#xff0c;以实现高性能和耐用性。Trench 9 MOSFET系列产品全部符合AEC-Q101标准&#xff0c;且超越了这一国际汽车级标准的要求&#xff0c;在包括温度循环 (TC)、耐高温栅极偏置 (HTGB)、耐高温反向偏置 (HTRB) 和断续工…

DBETR-1X/180G24K4M反馈型比例压力阀放大器

DBETR-1X/30G24K4M&#xff0c;DBETR-1X/315G24K4M&#xff0c;DBETR-1X/80G24K4M&#xff0c;DBETR-1X/180G24K4M&#xff0c;DBETR-1X/230G24K4M&#xff0c;DBETR-1X/350G24K4M比例溢流阀是一种遥控阀。其设计结构为座阀式直动溢流阀&#xff0c;搭配外置式比例放大器。 这…

挑选适合自己的英文原版书

很多人在阅读英文原版小说时感觉十分吃力&#xff0c;有很多生词或长难句。如何寻找适合自己英文阅读水平的书籍呢&#xff1f;下面推荐一种按蓝思值挑选英文原版书的方法。 首先根据自己的受教育程度&#xff0c;选择对应蓝思级别的英文书。如博士可以选择蓝思值为1300L的英文…

图神经网络:(图像分割)三维网格图像分割

文章说明&#xff1a; 1)参考资料&#xff1a;PYG的文档。文档超链。斯坦福大学的机器学习课程。课程超链。(要挂梯子)。博客原文。原文超链。(要挂梯子)。原文理论参考文献。提取码8848。 2)我在百度网盘上传这篇文章的jupyter notebook以及预训练模型。提取码8848. 3)博主水平…

qt信号与槽

信号与槽的概念&#xff1a; 1>信号&#xff1a;信号就是信号函数&#xff0c;可以是组件自身提供&#xff0c;也可以是用户自己定义&#xff0c;自定义时&#xff0c;需要类体的signals权限下进行定义&#xff0c;该函数是一个不完整的函数&#xff0c;只有声明&#xff0…

输入一个链表,输出该链表的倒数第 k 的结点

一、思路 假设 K 是 2&#xff0c;根据下面的图片可以看出&#xff0c;倒数第 K 个结点就是 45。 需要注意的前提是&#xff0c;K 不能是负数也不能是 0 并且也不能超过链表的结点个数&#xff0c;因为要保证 K 是在链表的范围里&#xff0c;才能找到 K&#xff0c;然后返回这…

【网络】TCP三次握手和四次挥手(感性理解)

目录 三次握手 文字描述三次握手过程 为什么是三次握手&#xff1f; 什么是SYN洪水&#xff1f; 连接和半连接队列 一次、两次握手行不行&#xff0c;四/五/六次握手行不行&#xff1f; 三次握手一定会成功吗&#xff1f; 三次握手的过程中可不可以携带数据 TCP中的IS…