指针大魔王(中)

2023-12-31 15:36
文章标签 指针 魔王

本文主要是介绍指针大魔王(中),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

         ✨✨欢迎大家来到贝蒂大讲堂✨✨

        ​​​​🎈🎈养成好习惯,先赞后看哦~🎈🎈

                 所属专栏:C语言学习        

                 贝蒂的主页:Betty‘s blog


目录

1. 引言

2. 二级指针

3. 数组与指针的关系

  3.1 数组名的理解

  3.2 sizeof与数组名

3.3 数组与指针等价关系 

3.4 指针数组 

(1)指针数组的概念

(2)指针数组的理解 

 (3)模拟二维数组

3.5 数组指针 

(1)数组指针的概念

 (2)数组指针的理解

 3.6 指针数组与数组指针的区别

3.7 字符串

3.8 数组传参

(1)一维数组传参

(2)二维数组的传参

结言

1. 引言

      前面给大家介绍了一些指针的基本概念,今天就让我们继续深入指针的世界,和贝蒂一起打败指针大魔王吧~

2. 二级指针

 指针变量也是变量,是变量就有地址,那我们就把存放指针变量地址的指针称为二级指针。

 可能理解起来有点绕,我们可以通过下面示意图演示一下

 代码如下

	int a = 10;int* pa = &a;//一级指针,存放a的地址int** ppa = &a;//二级指针,存放指针变量p的地址

贝蒂说:“不能直接把&&a赋值给ppa哦,因为&&在C语言中是且的意思” 

 (1)对ppa解引用,找到pa,也就是说*ppa==pa

 (2)对pa解引用,找到a,也就是说**ppa==a

	int* b = *ppa;//找到a的地址int c = **ppa;//找到a

贝蒂说:“以此类推,我们也能套娃出三级指针,四级指针啦~”

3. 数组与指针的关系

  3.1 数组名的理解

   我们在前面学习数组时就明白,数组名是首元素地址,但是讲解的不够深入,今天就让我们深入了解一下吧~

   首先让我们观察一下如下代码

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//&arr[0],arr,&arr的区别printf("&arr[0] = %p\n", &arr[0]);//首元素地址printf("arr = %p\n", arr);//一维数组数组名printf("&arr = %p\n", &arr);//对整个数组取地址return 0;
}

输出结果:

&arr[0] = 00DCF710
arr       = 00DCF710
&arr     = 00DCF710

  从结果来说&arr[0],arr,&arr到底有什么区别呢~

  让我们再看看下面这段代码

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };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);printf("&arr = %p\n", &arr);printf("&arr+1 = %p\n", &arr + 1);return 0;
}

输出结果:

&arr[0] = 00CFFE80
&arr[0]+1 = 00CFFE84
arr = 00CFFE80
arr+1 = 00CFFE84
&arr = 00CFFE80
&arr+1 = 00CFFEA8

 1. &arr[0]与arr+1都是跳过4个字节,相当于跳过1个整型元素。

 2. &arr+1跳过40个字节,相当于10个整型,也就是整个数组。

 总结:arr与&arr[0]都是首元素地址,指向数组第一个元素。&arr以首元素地址表示,但是指向的是整个数组。

  3.2 sizeof与数组名

    我们知道sizeof实际上是获取了数据在内存中所占用的存储空间,单位是字节

    让我们看看下面这段代码吧

#include <stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", sizeof(arr));//计算大小return 0;
}

输出结果:40

   不知道大家有没有疑惑~如果数组名是首元素地址的话,我们知道在32位机器上大小为4,在64位机器上大小为8。那为什么是40呢?

其实数组名就是数组⾸元素(第⼀个元素)的地址,但是有两个例外
• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节
• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)

   贝蒂说:“除了这两个例外,其他所有地方的数组名都是指首元素地址”

3.3 数组与指针等价关系 

   假设有一个一维数组和一个二维数组

int arr[5]={1,2,3,4,5};
int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}}

  我们要访问它的每个元素,有哪些方法呢~

  1. 数组访问 

	int arr1[5] = { 1,2,3,4,5 };for (int i = 0; i < 5; i++){printf("%d ", arr1[i]);}
int arr2[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 3; j++){printf("%d ", arr2[i][j]);}printf("\n");
}

2. 指针访问

for (int i = 0; i < 5; i++)
{printf("%d ", *(arr1+i));
}
for (int i = 0; i < 3; i++)
{for (int j = 0; j < 3; j++){printf("%d ", *(*(arr2 + i) + j));}
}

 通过对上面代码的观察,我们可以总结如下规律

1. arr[i]与*(arr+i)等价。

2. arr[i][j]与*(*(arr+i)+j)等价。

贝蒂说:“通过这两条规律,我们就可以解释很多事情啦~” 

3.4 指针数组 

(1)指针数组的概念

  指针数组顾名思义就是存放指针的数组 ,数组中每个元素都是指针,存放的都是地址

int*parr1[5];//存放五个整型指针变量
char*parr2[5];//存放五个字符指针变量
float*parr3[5];//存放五个浮点数指针变量

代码示例 

	int arr1[] = { 1,2,3 };int arr2[] = { 4,5,6 };int arr3[] = { 7,8,9 };//将每个数组的首元素地址都存进去int* parr[3] = { arr1,arr2,arr3 };

示意图

(2)指针数组的理解 
int main()
{int arr1[] = { 1,2,3 };int arr2[] = { 4,5,6 };int arr3[] = { 7,8,9 };int* parr[3] = { arr1,arr2,arr3 };printf("%p\n", parr);//打印指针数组首元素地址,也就是打印存放arr1空间的地址printf("%p\n", parr[0]);//arr1数组首元素地址printf("%p\n", *parr);//arr1首元素地址printf("%d\n", **parr);//相当于对arr1首元素地址解引用,指的的是1printf("%d\n", *parr[0]);//也相当于对arr1首元素地址解引用,为1printf("%d\n", *parr[1]);//相当于对arr2首元素地址解引用,为4return 0;
}

输出结果:

012FFE30
012FFE6C
012FFE6C
1
1
4

 (3)模拟二维数组

 通过上述我们对指针数组的理解,我们可以间接来模拟出二维数组。

代码如下:

int main()
{int arr1[] = { 1,2,3 };int arr2[] = { 4,5,6 };int arr3[] = { 7,8,9 };//将每个数组的首元素地址都存进去int* parr[3] = { arr1,arr2,arr3 };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 3; j++){printf("%d ", parr[i][j]);}printf("\n");//换行)}return 0;
}

贝蒂说:“模拟的二维数组并不是真正的二维数组,因为二维数组在内存中是连续存储的,而模拟出来的数组内存存储并不连续哦~”

3.5 数组指针 

(1)数组指针的概念

同理,指针数组的本质是一个数组;那么数组指针的本质就是个指针,指向一个数组的指针。

int(*parr1)[5];//指向一个有五个元素的整型数组
char(*parr2)[5];//指向一个有五个元素的字符数组
float(*parr3)[5];//指向一个有五个元素的浮点数数组
 (2)数组指针的理解
int main()
{int arr[5] = { 1,2,3,4,5 };int(*parr)[5] = &arr;//对数组名取地址代表整个数组的地址printf("%p\n", parr);//整个数组的地址一般用数组首元素地址表示printf("%p\n", parr[0]);//相当于*(parr+0)==arr,首元素地址printf("%p\n", *parr);//首元素地址printf("%d\n", **parr);//相当于对首元素地址解引用,指的的是1printf("%d\n", *parr[0]);//也相当于对首元素地址解引用,为1printf("%d\n", *parr[1]);//等价于*(*(parr+1)),parr+1跳过一个数组大小的地址,越界访问return 0;
}

输出结果:

012FF6F0
012FF6F0
012FF6F0
1
1
-858993460(越界访问,随机数) 

示意图:

 3.6 指针数组与数组指针的区别

   可能有许多小伙伴区别不清楚指针数组与数组指针,但是如果写成指针的数组,数组的指针,可能更好理解。接下来让我们具体分析一下吧~

   首先我们要清楚一个优先级顺序:()>[]>*

   1. 在int*parr[]中,parr先与[]结合(数组),而parr前面声明的变量类型是int*。所以这是一个数组,数组中每个元素的类型是int*的指针,这一类我们统称为指针数组

   2. 在int(*parr)[]中,parr先与*结合(指针),而后除开(*parr)是一个int []的数组类型。所以这是一个指针,这个指针指向的是一个数组,这一类我们称为数组指针。

 贝蒂说:“是不是一下子豁然开朗啦~”

3.7 字符串

 我们先看一下下面这段代码

char arr1[]="im betty";
char arr2[]={'a','b','c','\0'};

 这是一种常见的字符串的表示形式,以'\0'作为其结尾标志。

 但是还有另外一种表示形式,代码如下

	//const可以省略const char* p1 = "im betty";const char* p2 = "abc";

 我们知道const修饰在*前,不能改变指针变量所指向的值,所以这个字符串是不能改变的,我们称为常量字符串,也是和字符数组最直接的区别。 

 贝蒂说:“千万不要误认为把整个字符串存进p1,p2中喽,字符串在内存中是以首元素地址的存储的,也就是分别把i与a的地址存进p1,p2的指针变量中”

知道这些之后,让我们来看一道题吧

输出什么?

#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

输出结果:

str1 and str2 are not same
str3 and str4 are same 

  为什么会出现这种结果呢,那是因为这⾥str3和str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的⼀个内存区域,当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,每个数组地址就会不同。所以str1和str2不同,str3和str4相同。

3.8 数组传参

(1)一维数组传参

   我们在之前学习函数时候就讲过一维数组传参,让我们来复习一下吧。

   代码如下

void print(int arr[])//写成数组的形式
{int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };print(arr);//将数组传递给print函数return 0;
}

 我们传参是传的数组名,我们知道数组名是首元素的地址,既然是地址,自然就能用指针来接受,所以就有了另外一种写法。 

void print(int*p)//用指针来接收
{int i = 0;for (i = 0; i < 10; i++){printf("%d ",*(p+i));}
}
(2)二维数组的传参

  先让我们看看一般二维数组是如何传参的吧

void print(int arr[][3])//行可以省略,列不可以
{int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 3; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[3][3] = {{1, 2, 3}, { 4,5,6 }, { 7,8,9 }};print(arr);//将数组传递给print函数return 0;
}

那么指针接收如何写呢,还是int*p吗,我们知道二维数组可以看成把每一行当做一个元素的一维数组,数组名首元素地址自然是第一行元素的地址,所以要用数组指针来接收哦~ 

代码如下

void print(int(*p)[3])//明确元素个数
{int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 3; j++){printf("%d ",*(*(p+i)+j));}printf("\n");}
}

结言

    今天的内容比较多,也比较复杂,大家下来要多多复习哦~

这篇关于指针大魔王(中)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/556455

相关文章

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非

C和指针:字符串

字符串、字符和字节 字符串基础 字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。 字符串长度就是字符串中字符数。 size_t strlen( char const *string ); string为指针常量(const修饰string),指向的string是常量不能修改。size_t是无符号数,定义在stddef.h。 #include <stddef.h>

【C++】作用域指针、智能指针、共享指针、弱指针

十、智能指针、共享指针 从上篇文章 【C++】如何用C++创建对象,理解作用域、堆栈、内存分配-CSDN博客 中我们知道,你的对象是创建在栈上还是在堆上,最大的区别就是对象的作用域不一样。所以在C++中,一旦程序进入另外一个作用域,那其他作用域的对象就自动销毁了。这种机制有好有坏。我们可以利用这个机制,比如可以自动化我们的代码,像智能指针、作用域锁(scoped_lock)等都是利用了这种机制。

MFC中App,Doc,MainFrame,View各指针的互相获取

纸上得来终觉浅,为了熟悉获取方法,我建了个SDI。 首先说明这四个类的执行顺序是App->Doc->Main->View 另外添加CDialog类获得各个指针的方法。 多文档的获取有点小区别,有时间也总结一下。 //  App void CSDIApp::OnApp() {      //  App      //  Doc     CDocument *pD

C和指针:结构体(struct)和联合(union)

结构体和联合 结构体 结构体包含一些数据成员,每个成员可能具有不同的类型。 数组的元素长度相同,可以通过下标访问(转换为指针)。但是结构体的成员可能长度不同,所以不能用下标来访问它们。成员有自己的名字,可以通过名字访问成员。 结构声明 在声明结构时,必须列出它包含的所有成员。 struct tag {member-list} variable-list ; 定义一个结构体变量x(包含

hot100刷题第1-9题,三个专题哈希,双指针,滑动窗口

求满足条件的子数组,一般是前缀和、滑动窗口,经常结合哈希表; 区间操作元素,一般是前缀和、差分数组 数组有序,更大概率会用到二分搜索 目前已经掌握一些基本套路,重零刷起leetcode hot 100, 套路题按套路来,非套路题适当参考gpt解法。 一、梦开始的地方, 两数之和 class Solution:#注意要返回的是数组下标def twoSum(self, nums: Lis

Qt: 详细理解delete与deleteLater (避免访问悬空指针导致程序异常终止)

前言 珍爱生命,远离悬空指针。 正文 delete 立即删除:调用 delete 后,对象会立即被销毁,其内存会立即被释放。调用顺序:对象的析构函数会被立即调用,销毁该对象及其子对象。无事件处理:如果在对象销毁过程中还涉及到信号和槽、事件处理等,直接 delete 可能会导致问题,尤其是在对象正在处理事件时。适用场景:适用于在确定对象已经不再被使用的情况下,并且不涉及异步处理或事件循环中的

C语言进阶版第8课—指针(2)

文章目录 1. 数组名的理解2. 指针访问数组3. 一维数组传参本质4. 冒泡排序5. 二级指针6. 指针数组7. 指针数组模拟二维数组 1. 数组名的理解 sizeof(数组名)— 这里的数组名代表整个数组,计算的也是整个数组的大小&数组名 — 这里的数组名代表是整个数组,取出的是整个数组的地址除了以上两种,其他任何地方使用数组名,数组名都表示首元素的地址 //数组名

有希带你深入理解指针(4)

目录 前言🥰1.回调函数😺1.1回调函数的概念😋 2.qsort使用🤯2.1什么是qsort👻2.2 qsort函数的使用🧐 3.模拟实现qsort😎 前言🥰 本篇文章是对指针知识的进一步讲解,如果对部分知识有不了解的地方可以移步前文进行学习!😶‍🌫️ 1.回调函数😺 1.1回调函数的概念😋 回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的