C++指针与数组:指针详解

目录
  • 一. What(什么是指针)
    • 1. 地址初了解
    • 2. 指针概念
    • 3. 指针与指针变量
  • 二. Why(为什么要有指针)
  • 三. How(如何使用指针)
    • 1. 基本定义
    • 2. 取地址操作符 &
    • 3. 解引用操作符 *
    • 4. 结构体指针
    • 5. 多级指针
    • 6.指针变量的命名规范
  • 四. 我对指针的一些理解
  • 总结

本文主要介绍C语言中指针的基本概念与用法,本博文从零开始讲解指针,只要你了解基本的C语法,相信你看完本文后一定会有所收获。

一. What(什么是指针)

1. 地址初了解

要搞明白什么是指针,首先我们得先了解一个概念:地址。

什么是地址呢?

我们知道,计算机的内存是一块连续的大块存储空间,为了提高 CPU 查找数据的效率,我们将这块连续的存储空间以字节为单位进行编号,每个字节都一一对应了一个编号,这个编号就叫做地址。

举个形象的例子:

如果我们把一座宿舍楼看做是内存,那么宿舍楼里的房间我们就可以看成是内存里的每一个字节,那么每个房间的门牌号就可以看做是地址。

为什么要有地址?

通过上面的例子,我们很容易了解到地址存在的意义。试想,如果宿舍楼里的房间都没有门牌号,我们就很难描述一指定房间的具体位置。CPU 对内存的处理也是如此,如果没有地址,CPU 就没有办法确定指定内存的具体位置,从而正确且快速的访问到该内存空间。

2. 指针概念

  • 指针就是地址
  • 指针就是地址
  • 指针就是地址

重要的事情说三遍,我们必须明确的知道一件事,指针就是地址,可以理解为,指针是地址在C语言里的一种特殊叫法。

#include <stdio.h>
int main()
{
	int a = 10;
	int *p = &a;		// p是不是指针?
	printf("%p\n", p);  // %p 可以格式化输出地址。
	return 0;
}

既然如此,上述代码中 p 是不是指针呢?

我们说过指针就是地址,而 p 是不是地址?当然不是!地址是一个单独的数据,就像 int a = 10; 我们能说 a 就是 10 吗?当然不能,10是一个整形常量,而 a 是一个整形变量,我们只能说 a 里面存放的值是 10,而不能简单的说 a 就是 10。

同理,上述代码中的 p 是一个变量,里面存放的是 a 的地址,我们称它为指针变量。所以严格意义上来说,p 不是指针,而是指针变量。就像 a 不是整数10,而是整形变量一样

那么什么是指针呢,我们把这段代码运行起来:

没错,严格意义上来说,这段数字才真正的叫做指针 (地址值在计算机中一般以16进制显示)。

3. 指针与指针变量

通过上面的讲解,相信大家可以理解,指针是地址,是一个常量,而指针变量是一个变量,里面存放的是指针,这两者之间有着本质区别。

看到这,相信有些同学就有疑问了:明明上课的时候,或者某些书上都将指针变量统称为指针,就像 上例中的 p 平时都叫做指针,为什么现在又说 p 不是指针呢?

请大家注意,我说 p 不是指针是在严格意义上来说。

在日常使用中,我们经常把指针和指针变量混为一谈。这似乎成为了一种习惯,甚至我在下面的表述中都可能将指针变量表述为指针。

结论是:我们在日常使用中,可以模糊指针与指针变量的概念,把指针变量统称为指针,但是,我们心里必须清楚的意识到,指针和指针变量的本质区别,以及你叫的这个东西到底是指针还是指针变量。

二. Why(为什么要有指针)

经过上面的讲解,相信这个问题就非常简单了。指针就是地址,那么为什么要有地址?究其原因就是,为了提高查找效率

试想如果没有地址的存在,我们写下 int a = 10; 的时候,系统会自动在内存中开辟一4字节的空间存放10这个数值。而我们 CPU 在访问的时候,由于没有地址的存在只能在内存中一块一块的遍历对比查找,而有了地址,内存就可以把地址值告诉 CPU,CPU 就可以根据地址值直接定位到该内存,大大提高了 CPU 访问内存的效率与准确性。

三. How(如何使用指针)

1. 基本定义

指针变量的定义格式较简单,只要在类型和变量名之间加上 * 操作符,就可以定义该类型的指针变量。

int *a = NULL;				//定义整形指针变量
double *d = NULL;			//定义双精度浮点型指针变量
struct stdnt *ps = NULL;	//定义结构体指针变量

注: NULL 是指针变量初始化的时候常用的宏定义,称其为空指针,本质为 0 值。

如果定义时不初始化,如:

int *a;

我们知道,变量如果不初始化,其中的值就是随机值,也就是此时的指针变量 a 里面存放的是随机值,如果此时访问 a 变量就会以这个随机值作为地址访问对应的内存,这种操作是非法的。这种不初始化的指针我们称之为野指针。

所以,为了避免野指针的出现,我们在定义指针变量时尽量对其进行初始化。

2. 取地址操作符 &

我们使用指针就是使用地址,那么地址从何而来?我们可不可以这么写代码:

int *a = 0x11223344;

我们把 0x11223344 看做是地址值交给整形指针变量 a 来存储。语法上没有问题,但是这样写毫无意义!因为 0x11223344 这个地址对于我们来说是未知的,我们并没有读取和访问的权限,这时候的 a 变量就相当于一个野指针。

事实上,我们的地址是由 & 取地址符取出来的。

#include <stdio.h>
int main()
{
	int a = 10;
	int *p = &a;
	return 0;
}

这里我们定义了整形变量 a,取地址符取出了 a 的地址并把它赋给整形指针变量 p。

这里就有一个疑问,a 是一个整形变量,占4字节的空间,而通过上面的介绍,我们知道内存的地址编号是以字节为单位的,这就意味着变量 a 其实存在4个地址,那么 &a 究竟取出的是哪一个地址呢?

上结论:

在C语言中,对任意类型的变量或者数组取地址,永远取得是数值上最小的那个地址。

我们画一下上面代码的内存分布图:

假设从左向右地址依次升高,那么 p 里面存放的就是 a 变量4字节中地址最低的那个字节的地址。

3. 解引用操作符 *

我们上面说到,对变量取地址取得是地址最低的字节即首字节地址,那么我们如何通过这一个字节的地址访问到原变量里的数据呢,这里就需要使用 * 操作符:

#include <stdio.h>
int main()
{
	int a = 10;
	int *p = &a;
	*p = 20;     // 等价于 a = 20
	printf("%d\n", *p);
	printf("%d\n", a);
	return 0;
}

看上面的代码,我们把 a 变量的地址赋值给指针变量 p,通过 *p 即可访问到该变量的内容。

那么 * 操作符到底是如何使用的呢,下面给出结论:

对指针解引用,就是指针指向的目标。

就像上例中 *p 是什么?*p 就是 a,*p 就是 a,*p就是a,重要的事情说三遍。所以,如果我改变 *p 的值,完全等价于直接改变 a 变量的值。

上面的代码运行结果:

那么,我们看下面这段代码:

int main()
{
	int a = 0x11223344;
	int *p = &a;
	printf("0x%x\n", *(char*)p);
	return 0;
}

注意,我们说 *p 就是 a,但是,这里的 *p 被强制类型转化为 char* 类型之后再进行的解引用操作,此时的 p 就是一个字符类型的指针,因为字符 char 类型在C中只占一个字节,对其进行解引用只能访问一个字节的内容。而我们说过 p 中存放的是 a 变量的首字节的地址,即 44 的地址 (至于为什么,请读者自己了解大小端的内容),解引用就只访问到了 44:

4. 结构体指针

下面定义一个结构体:

typedef struct Student
{
	int age;
	char name[20];
	char sex[2];
}STD;

C语言中任何数据类型都有其对于的指针类型,结构体也不例外,如下:

int main()
{
	STD s = { 18, "张三", "男" };
	STD *ps = &s;		// 定义结构体指针
	printf("%s\n%d\n%s\n", s.name, s.age, s.sex);
}

我们定义一个结构体变量并初始化,通过 . 操作符可以很容易访问到结构体的内容。那么我们如何通过结构体指针变量 ps 来访问结构体的内容呢?

我们说过对指针解引用,就是指针指向的目标,那么请读者思考,*ps 是什么呢?没错 *ps 就是 s变量,既然如此,我们就可以这么访问:

	printf("%s\n%d\n%s\n", (*ps).name, (*ps).age, (*ps).sex);
	//注:因 . 操作符优先级高于 * 操作符,故 (*ps) 必须加括号

C语言可能认为这样写太麻烦,于是对于结构体指针我们可以使用 -> 操作符直接访问到结构体的内容:

	printf("%s\n%d\n%s\n", ps->name, ps->age, ps->sex);

ps->name 写法完全等价于 (*ps).name 这样的写法。
注:-> 操作符只在结构体指针变量访问结构体成员时使用。

5. 多级指针

首先问大家一个问题,指针变量是不是变量?是变量。既然是变量,那么就有地址,我们依然可以把指针变量的地址存入一个新的变量,此变量我们可以称为二级指针变量:

int a = 10;
int *pa = &a;
int **ppa = &pa;  // 定义二级指针变量
*ppa;  //等价于 pa
**ppa; //等价于 *pa,即等价于 a

二级指针大家不要看的多么神秘,所谓二级指针其实也就是一个指针变量,只不过里面存放的是另一个指针变量的地址而已。
既然如此,二级指针变量是不是变量呢?它能不能取地址存入另外一个三级指针变量呢?那么,三级指针变量是不是变量呢?… 如果你愿意,可以一直这么套娃下去~

6.指针变量的命名规范

之所以把这块单独分出来讲,是因为对指针变量进行一个好的命名规范,不仅有利于提高代码的可读性,更有助于我们理解一些复杂的数据结构的代码。

指针的命名习惯上在原变量名前加字母 p。 如定义整形变量 a ,其指针变量就命名为 pa, 定义结构体变量 std 其对应指针变量命名为 pstd,如果对 pstd 再次取地址存入二级指针变量,那么该二级指针变量就应该命名为 ppstd。

四. 我对指针的一些理解

都说指针是C语言的灵魂,那么指针的魅力究竟在何处?

我们看这段交换两个数的代码:

#include <stdio.h>
void Swap(int a, int b)
{
	int t = a;
	a = b;
	b = t;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	return 0;
}

我们应该知道,这段代码是无法完成 a, b 的交换的,因为 Swap() 里的 a, b 只不过是 main() 函数里 a, b 的一份拷贝。即,Swap() 函数里 a, b 的改变是不会影响 main() 函数的。这种传参的方式我们称之为传值。

可是我们这么写:

#include <stdio.h>
void Swap(int *pa, int *pb)
{
	int t = *pa;
	*pa = *pb;
	*pb = t;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(&a, &b);
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	return 0;
}

main() 传参时传 a, b 的地址,Swap() 函数使用指针进行接收,内部使用解引用方式进行交换,我们就可以完成真正的交换功能。这种传参的方式我们称之为传址。

我在一篇文章上看到这样一个说法,可谓是对指针理解到了本质:

  • 传值就是传值。
  • 传址就是传值本身。

值和值本身两个概念希望大家能够深刻理解。

如同我们 Swap() 函数的第一种写法,main() 函数仅仅是将 a, b 的值传递给了 Swap() 函数进行处理,可无论 Swap() 函数对其做什么样的处理,也依然无法改变原来 a, b 的值。

而 Swap() 的第二种写法,main() 函数传的是 a, b 的地址,相当于传的是 a, b 本身,这样 Swap() 在进行处理时便可直接改变原来的 a, b 的值。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • 深入了解c++数组与指针

    1.数组 数组大小(元素个数)一般在编译时决定,也有少部分编译器可以运行时动态决定数组大小,比如icpc(Intel C++编译器). 1.1数组名的意义 数组名的本质是一个文字常量,代表数组第一个元素的地址和数组的首地址.数组名本身不是一个变量,不可以寻址,且不允许为数组名赋值.假设定义数组: int A[10]; 那么再定义一个引用: int* &r=A; 这是错误的写法,因为变量A是一个文字常量,不可寻址.如果要建立数组A的引用,应该这样定义: int* const &r=A; 此时

  • 浅谈C/C++中指针和数组的不同

    这边先简单介绍一下内存分区. 内存按照用途划分为五个区: 1.栈区:由系统控制分配和回收. 例如定义变量 int x = 0; int *p = NULL; 变量所占的内存都是分配在栈区的. 2.堆区:由程序员管理. 在C语言中由 malloc 申请的内存,或者在C++中,用 new 申请的内存,是在堆区中申请的.用完之后需要程序员自己回收,否则会造成内存泄漏. 3.全局区:存储全局变量及静态变量 4.常量区:存储常量. 5.代码区:存储编译之后的二进制代码. 数组和指针具有很大的相似性,实际上

  • 详解C++中的指针、数组指针与函数指针

    C++中一个重要的特性就是指针,指针不仅具有获得地址的能力,还具有操作地址的能力.指针可以用于数组.或作为函数的参数,用来访问内存和对内存的操作,指针的使用使得C++很高效,但是指针也非常危险,使用不当会带来比较严重的问题. 1.指针 程序中所有的变量和常量都存在一个内存地址中,当然,函数也有对应的内存地址,内存地址的不同会导致程序执行时有所不同. 指针就是用来控制和存储内存地址的变量,它指向单个对象的地址,除了void之外,指针的数据类型与所指向地址的变量数据类型保持一致. 2.如何定义指针.

  • C++中的数组引用和指针引用

    目录 C++中的数组引用和指针引用 一.引用的本质 二.数组的引用 三.指针的引用 C++中的数组引用和指针引用 一.引用的本质 我们在讲解引用之前需要知道为什么C++中会单独提出引用这个概念,在前面也提到在C++从一定角度上是C语言的升级版,其实引用时和C语言中的指针一样的功能,并且使得语法更加简洁.既然提到和指针功能相同,那么引用的功能其实就是给空间取别名. 代码解析: #define _CRT_SECURE_NO_WARNINGS #include<iostream> using nam

  • 详解C++中的对象指针与对象数组

    C++对象指针 指向对象的指针 在建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放其成员.对象空间的起始地址就是对象的指针.可以定义一个指针变量,用来存放对象的指针. 如果有一个类: class Time { public : int hour; int minute; int sec; void get_time( ); }; void Time::get_time( ) { cout<<hour<<":"<<minute<<

  • C语言 指针与数组的详解及区别

    C语言 指针与数组的详解及对比 通俗理解数组指针和指针数组 数组指针: eg:int( *arr)[10]; 数组指针通俗理解就是这个数组作为指针,指向某一个变量. 指针数组: eg:int*arr[10]; 指针数组简言之就是存放指针的数组: --数组并非指针&&指针并非数组 (1)定义一个外部变量: eg:int value=10; int *p=&value; 举例:当需要在一个函数中用这个变量时:externa int*p;而非extern int p[]; 分析:当用:e

  • C语言中指针和数组试题详解分析

    目录 数组题: 程序一(一维数组): 字符数组 程序二(字符数组): 程序三(字符数组): 程序四(字符数组): 程序五(字符数组): 二维数组 程序六( 二维数组): 指针题 程序七( 指针): 程序八( 指针): 程序九( 指针): 程序十( 指针): 程序十( 图): 程序十一( 指针): 程序十二( 指针): 程序十三( 指针): 指针 和 数组 试题解析 小编,在这里想说一下,c语言的最后一节 C预处理,可能还需要一些时间,因为小编,昨天才下载了虚拟机 和 linux 系统,还没开始安

  • C++中指针指向二维数组实例详解

    C++中指针指向二维数组实例详解 一维指针通常用指针表示,其指向的地址是数组第一元素所在的内存地址,如下 int ary[4][5]; int(*aryp)[5] = ary; 那么ary[4]相当于int(*aryp),以下理解如此,但参数传递需要知道实参所在 的一维个数,所以传递的时候应该传递多一个参数,子数组的引用可以理解 为(*p),那么取元素就是(*p)[i],如下 void printVal(int(*aryp)[5],int irowCount){ for (int(*p)[5]

  • C语言指针数组案例详解

    指针与数组是 C 语言中很重要的两个概念,它们之间有着密切的关系,利用这种 关系,可以增强处理数组的灵活性,加快运行速度,本文着重讨论指针与数组之 间的联系及在编程中的应用. 1.指针与数组的关系 当一个指针变量被初始化成数组名时,就说该指针变量指向了数组.如: char str[20], *ptr; ptr=str; ptr 被置为数组 str 的第一个元素的地址,因为数组名就是该数组的首地址, 也是数组第一个元素的地址.此时可以认为指针 ptr 就是数组 str(反之不成立), 这样原来对数

  • C语言 指针数组进阶详解

    目录 指针与数组中的sizeof与strlen sizeof strlen 数组名 1.一维数组 整型数组 字符数组 指针数组 2.二维数组 指针笔试题 笔试题1 笔试题2 笔试题3 笔试题4 笔试题5 前言:指针与数组的知识往往让我们无法给自己定位,似乎是懂了,但真的碰上了又一言难尽.接下来有一些关于指针与数组的知识和例题讲解,来看看你对指针和数组到底有多了解吧! 指针与数组中的sizeof与strlen sizeof sizeof值关注占用空间的大小,单位是字节,不关注元素的类型,是一个操作

  • C/C++指针介绍与使用详解

    目录 什么是指针 定义指针变量 间接引用指针 常or常常 指向指针的指针 指针与数组 指针的运算 堆内存分配 C语言 C++语言 指针与函数 数组名作为函数的入口参数 函数名作为参数传入其他函数 使用指针修改函数参数 变量的引用作为函数的参数 两个常用的字符串函数 总结 什么是指针 C/C++语言拥有在程序运行时获得变量的地址和操作地址的能力,这种用来操作地址的特殊类型变量被称作指针. 翻译翻译什么tmd叫tmd指针! 变量或常量的指针存储的数据是 :对应的变量或常量在内存中的地址. 图解: 此

  • C++ 中引用与指针的区别实例详解

    C++ 中引用与指针的区别实例详解 引用是从C++才引入的,在C中不存在.为了搞清楚引用的概念,得先搞明白变量的定义及引用与变量的区别,变量的要素一共有两个:名称与空间. 引用不是变量,它仅仅是变量的别名,没有自己独立的空间,它只符合变量的"名称"这个要素,而"空间"这个要素并不满足.换句话说,引用需要与它所引用的变量共享同一个内存空间,对引用所做的改变实际上是对所引用的变量做出修改.并且引用在定义的时候就必须被初始化.     参数传递的类型及相关要点: 1 按值

  • C++ 中引用和指针的关系实例详解

    C++ 中引用和指针的关系实例详解 1.引用在定义时必须初始化,指针没有要求 int &rNum; //未初始化不能通过编译 int *pNum; //可以 2. 一旦一个引用被初始化为指向一个对象,就不能再指向 其他对象,而指针可以在任何时候指向任何一个同类型对象 int iNum = 10; int iNum2 = 20; int &rNum = iNum; &rNum = iNum2; //不能通过 3. 没有NULL引用,但有NULL指针. int *pNum = NULL

  • C++ 通过指针实现多态实例详解

     C++ 通过指针实现多态实例详解 1.父类(DBConnector) 1)DBConnector.h #include <string> using namespace std; class DBConnector { private: string name; public: DBConnector(); DBConnector(string _name); ~DBConnector(); void show(); }; 2)DBConnector.cpp #include "D

随机推荐