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

这篇具有很好参考价值的文章主要介绍了【C语言进阶篇】之指针进阶(一)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


【C语言进阶技巧】指针掌握之道:解密指针的奇妙世界(第一部))
【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言

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

【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言

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

运行结果如下:
【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言
可以看到数组名和&数组名打印的地址和首元素打印的地址是一样的,这是不是意味着它们是一样的呢?很明显通过代码我们也可以发现,它们并不是相同的,因为&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;
}

运行的结果:
【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言
        可以看见输出的是一段地址,这两个地址都是函数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;
}

运行结果如下:【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言

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

5.3 两行有趣的代码

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

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

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

【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言

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

解析:
【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言
          我们可以观察到signal函数的返回类型和其中一个参数是相同的都是函数指针类型,下面我们利用别名typedef来使这段代码看起来更加易懂:

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

          CSDN上的别名没有高亮,但VS2019里面是有高亮的,定义函数后,你会发现编译器是不会报错,说明这样是可行的。
【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言

【C语言进阶篇】之指针进阶(一),C语言进阶篇,指针,程序人生,进阶C语言,c语言文章来源地址https://www.toymoban.com/news/detail-534005.html

到了这里,关于【C语言进阶篇】之指针进阶(一)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 哈工大CSAPP程序人生大作业

    正在上传…重新上传取消 计算机系统 大作业 题     目   程序人生 -Hello’s P2P  专       业    计算机科学与技术        学    号   2021110991             班    级      2103101             学       生         安心           指 导 教 师    

    2023年04月24日
    浏览(72)
  • 程序人生——Java数组和集合使用建议(2)

    程序人生——Java数组和集合使用建议(2) 需求:要删除一个ArrayList中的20-30范围内的元素;将原列表转换为一个可变列表,然后使用subList获取到原列表20到30范围内的一个视图(View),然后清空该视图内的元素,即可在原列表中删除20到30范围内的元素 建议72:生成子列表后

    2024年03月19日
    浏览(50)
  • 【程序人生】上海城市开发者社区小聚有感

    📫作者简介: 小明java问道之路 , 2022年度博客之星全国TOP3 ,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。          📫 热衷分享,喜欢原

    2024年02月06日
    浏览(68)
  • 程序人生 | 编程的上帝视角应该怎么去找

      前言 📫 作者简介 :小明java问道之路,专注于Linux内核/汇编/HotSpot/C++/Java/源码/架构/算法 就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫  🏆 CSDN专家博主/Java优质创作者/CSDN内容合伙人 、InfoQ签约作者 、阿里云专家/签约博主、

    2023年04月24日
    浏览(89)
  • 【程序人生】如何在工作中保持稳定的情绪?

    在工作中保持稳定的情绪是现代生活中一个备受关注的话题。随着职场压力和工作挑战的增加,我们常常发现自己情绪波动不定,甚至受到负面情绪的困扰。然而,保持稳定的情绪对于我们的工作效率、人际关系和整体幸福感都至关重要。 无论你是一位职场新人还是一位资深

    2024年02月15日
    浏览(46)
  • C罗老矣,我的程序人生还有多远

    ☆ 随着12月11号摩洛哥1-0葡萄牙比赛的结束,不仅说明葡萄牙对要结束本届卡塔尔世界杯了,就连C罗此生的世界杯之旅也将画上句号了。 ☆ 37岁的球星本该是人生最璀璨的阶段,但在足球生涯中,这已经是大龄了。不禁让我想到,身为开发的我,也大概类似吧。   目录  1、

    2024年01月16日
    浏览(52)
  • 【程序人生】还记得当初自己为什么选择计算机?

            还记得人生中第一次接触计算机编程是在高中,第一门编程语言是Python(很可惜由于条件限制的原因,当时没能坚持学下去......现在想来有点后悔,没能坚持,唉......)。但是,错过的就错过了,把握当前才是正确的选择。努力最好的时机永远是在过去,其次就在当

    2024年02月04日
    浏览(65)
  • 《人生苦短,我学Python》——第一个python程序

    Hello!朋友们大家好,从今天开始,我们将学习 Python 的相关内容。 首先,让我们来思考一个问题,编程是什么? 编程是人类与电脑沟通的过程,可以告诉电脑做什么以及怎么做。人类用编程构建数字世界,比如网站、App、办公软件等等。 世界上有很多种编程语言,比如C,

    2024年02月11日
    浏览(45)
  • 人工智能AI时代:全栈程序员的人生规划

    博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客👦🏻 《java 面试题大全》 🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭 《MYSQL从入门到精通》数据库是开发者必会基础之一~ 🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄

    2024年02月11日
    浏览(85)
  • 程序人生 | 与足球共舞的火柴人(致敬格拉利什,赋予足球更深的意义)

    个人简介 👀 个人主页: 前端杂货铺 🙋‍♂️ 学习方向: 主攻前端方向,也会涉及到服务端 📃 个人状态: 在校大学生一枚,已拿多个前端 offer(秋招) 🚀 未来打算: 为中国的工业软件事业效力n年 🥇 推荐学习:🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2Vue3项目实战 🥝

    2024年02月02日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包