c++ primer plus学习笔记(2.5)——数据类型(指针部分)

本来准备在第二章讲完数据类型的,后来发现实在太多了。虽然c++使用指针的程度不如c,但是我还是想把“c语言的灵魂”详细地讲讲。

目录

1 指针是什么?

1.1 取地址运算符 &

 1.2 解引用运算符 *

2 进一步理解指针 

2.1 硬币模型 (形象)

2.2 内存空间模型(本质)

3 使用指针

3.1 声明语法

3.2 空指针

3.3 手动管理内存:new 和 delete

3.4 只读指针,const指针

4 数组和指针

4.1 数组名的意义 

4.2 指针算术

4.3 数组表示法与指针表示法

5 二维数组、二维指针



回忆之前说的,一个变量包括三个基本属性:存储的信息的类型、内容、存储位置(地址)。信息的类型、内容分别在声明、赋值语句处标明。而信息的存储位置似乎没什么用。这里引出一个问题,:我们为什么需要知道变量的存储位置?知道后有什么用处?读者可以带着这个问题阅读本章。

1 指针是什么?

指针是一种特殊的数据类型,要与已有的数据类型组合才有意义;其值存储该种(与其组合的)数据类型的地址。int型指针便可存储某个int型变量的地址。声明格式如下:

type * name;        //在type后加上 * ,以表明这是一个这种类型的指针

1.1 取地址运算符 &

声明任意一种变量后,我们便可在变量左侧使用 & 来获得该变量的地址,如:

    int a = 10;
    int *p = &a;          //p是int型的指针,初始化为变量a的地址
    cout << p;            //输出a的地址

 1.2 解引用运算符 *

 声明任意一种指针后,我们便可在指针左侧使用 * 来获得该地址上的值,如:

    int a = 10;
    int *p = &a;          //p是int型的指针,初始化为变量a的地址
    cout << *p;           //输出a的值,10

2 进一步理解指针 

2.1 硬币模型 (形象)

    int a = 10;
    int *p = &a;          

如上述代码,有以下等价关系:

a == *p;

&a == p;

好比一枚硬币,其正面是值,背面是地址。对硬币正面使用 & 运算符,相当于使用硬币背面;对硬币背面使用 * 运算符,相当于使用硬币正面。换而言之,& 和 * 是将硬币翻面的运算。

2.2 内存空间模型(本质)

 声明语句构建了部分内存和变量名称之间的所属关系,int a 即表明分配了4字节的内存,可以使用变量名a进行追踪。而指针的值是1000,这表明将直接使用内存地址进行追踪,而不是使用变量名a。

3 使用指针

3.1 声明语法

已知同类型变量可在同一行声明,但指针略有不同,如:

int *a, b;    //a是int指针,b是int

上述代码告诉我们声明时,要在每个指针前面写上 * 。也可理解为对该变量解引用后才是该种数据类型(意即该变量是指针)。

3.2 空指针

未初始化的指针指向的位置是未知的。使用未初始化的指针就是使用未知内存,可能会导致系统崩溃,十分危险。如果确实在声明时没有初始化的目标,c++提供了空指针nullptr(c风格是NULL)以供解决风险。

nullptr:本质上就是0,对指向nullptr的指针解引用会直接终止程序,如:

    int *p = nullptr; //或 int *p = NULL;
    cout << p;        //输出0
    cout << *p;       //程序终止
    cout << "here";   //不执行

3.3 手动管理内存:new 和 delete

直接声明的变后,c++将自动管理相应的内存。但是,我们也可通过new和delete运算符来手动管理内存。new 运算符将在堆内存(详见内存空间章节)中分配相应大小的内存,并返回该内存首地址的指针。delete将指针指向的堆内存释放。声明语法如下:

type *point_name = new type;

delete point_name;

    int *p = new int;
    *p = 10;
    cout << *p;
    delete p;            //一定要记得释放

同时,也能使用new和delete声明数组:

type *point_name = new type [size];

delete [] point_name;        //注意这个方括号

    int *p = new int[10];
    p[1] = 10;
    cout << p[1];
    delete[] p;

 由此可见,可以直接将指针名视为数组名(因为在c++内部数组与指针基本等价,详见后文)。

使用new和delete有以下规则:

  • delete只能释放堆内存,即不使用delete释放不是new分配的内存
  • delete只能释放一次(多次释放将报错)
  • 使用new[],则用delete[];使用new,则用delete
  • delete 指向nullptr的指针不会有问题

3.4 只读指针,const指针

 注意指针的声明:type * name,name前共有三个位置可以放const,区别如下:

    const int *a = new int;
    int const *b = new int;
    int *const c = new int;
    *a = 10;    //报错
    *b = 10;    //报错
    *c = 10;
    delete a;   //记得释放
    delete b;
    delete c;

const的位置只有在 * 前后的区别,

* 前表示指向的内容不可被修改, * 后表示指向的地址不可被修改。

前者(例中的a,b)可以指向多个地址,但是不能修改它们,是只读指针

后者(例中的c)只能指向初始化时安排的地址,即这个指针的值不能改变,是const指针

ps:实际上,我们经常把只读指针说成const指针,因为const指针用处不大。

4 数组和指针

4.1 数组名的意义 

 你认为下述代码会输出什么? 

    int a[10] = {1, 2, 3};
    cout << a;

回忆数组的定义——在内存空间中连续放置的多个变量的整体,这个整体就是这一段连续内存的首地址。所以前文使用new分配的数组能使用int指针指向,且指针名和普通声明的数组名一样。

    int *p = new int[10];
    p[0] = 10;
    cout << p << '\n';
    cout << *p << '\n';    //输出10
    int a[10] = {20};
    cout << a << '\n';
    cout << *a << '\n';    //输出20

数组的首地址,便是第一个元素的地址,对数组名解引用便能得其值。

4.2 指针算术

指针也是变量,也有相应的运算符,p代表指针,n代表常数,具体如下:

*p 获得指针指向的值

&p 获得指针的地址

p + n 获得向后偏移n个单位(根据p指向的类型)的地址

p - n 获得向前偏移n个单位(根据p指向的类型)的地址

p1 - p2 获得两个指针在内存空间中的距离

p++ 等同于p = p + 1        //p--,--p,++p同理

#include <iostream>
#include <iomanip>
using namespace std;
#define show(x) cout << #x ":\r\t" << setw(2) << x << endl;

int main()
{
    int *a = new int, *b = new int, x, *c = &x;
    show(a);
    show(b);
    show(c);
    show(a + 1); //int是4字节,所以增加4(1个4字节)
    show(a - 2); //减少8(2个4字节)
    show(b - a); //堆内存之间的变量离得很近
    show(a - b);
    show(a - c); //栈内存离堆内存很远
    delete a;
    delete b;
}

我的电脑上的输出:

a:      0xe91600
b:      0xe91620
c:      0x61fdf4
a + 1:  0xe91604
a - 2:  0xe915f8
b - a:   8
a - b:  -8
a - c:  2213379

4.3 数组表示法与指针表示法

根据指针算术,这两句等价: 

a[i];

*(a + i);        // *a 等同于 *(a+0) 

因此,很多情况下数组和指针是有共性的, 你甚至可以想象:

int a[10];
int * const a = new int[10];

这两者在使用上是等价的,区别是内存管理手段不同。 

而区别之一是:

  • 数组名是const指针
  • 对数组名使用 sizeof 运算符,得到整个数组大小;指针则是自身长度
  • 对数组名使用 &,得到指向数组的指针;指针则是升维

请注意下述代码: 

#include <iostream>
#include <iomanip>
using namespace std;
#define show(x) cout << #x ":\r\t\t" << setw(2) << x << endl;

int main()
{
    short a[10] = {22}, *p = a;
    show(sizeof(&p)); //输出8,short二维指针的大小
    show(sizeof(p));  //输出8,short指针的大小
    show(sizeof(*p)); //输出2,short的大小

    show(sizeof(&a)); //输出8,指针的大小,指向short数组
    show(sizeof(a));  //输出20,short数组的大小,指向第一个short
    show(sizeof(*a)); //输出2,第一个元素的大小

    show(a);
    show(&a + 1); //输出+20,单位是数组长
    show(a + 1);  //输出+2,单位是short
    show(*a + 1); //输出23,第一个元素增加
}

参考输出:
sizeof(&p):      8
sizeof(p):       8
sizeof(*p):      2
sizeof(&a):      8
sizeof(a):      20
sizeof(*a):      2
a:              0x61fdf0
&a + 1:         0x61fe04
a + 1:          0x61fdf2
*a + 1:         23

再次可以看到,

  • 数组名就是指向第一个元素的指针;
  • 取地址是指向本数组的指针;
  • 解引用是第一个元素。
  • 指针加法的单位就是指针指向的大小。

声明一个5维指针z保证赋值出错,看vscode编辑器的报错可以帮助大家理解:

 

5 多维数组、多维指针

本节十分复杂,无意深入者可略过。

5.1 多维指针

首先,维数意味着什么?指针可以指向指针,把基本的数据类型定义为0维,每一级指针便增加一级维数。如:

int a0;    //0维
int *a1;   //1维指针
int **a2;  //2维指针
int ***a3; //3维指针

 使用时,每用一个 & ,维数上升1;每用一个 * ,维数下降1。维数降至0时,便是原数据。如:

 

5.2 * [] 组合

 分析下列代码:

    int a[10][5];    //a是个二维数组,10行5列
    int *b[10];      //b是个指针数组,包含10个int*
    int **c;         //c是个指针,指向int*
    int(*d)[10];     //d是个指针,指向一个大小为10的int数组

ps:因为 [] 的 优先级比 * 高,所以产生了b和d的区别

 b的声明的重要意义之一是管理多个不等长的数组

 

5.3 二维数组

对于二维数组a,有以下等价表示:

a[y][x];

*(*(a + y) + x);

*(a[y] + x);        //不好的表达

有实用价值的语句是:

    int a[10][5]; //a是个二维数组,10行5列
    &a;           //int (*)[10][5]
    a;            //int (*)[5]
    a[0];         //int * ,指向第一行
    a[0][0];      //int,第一行第一列

 因为 * 放在操作数左边, [] 放在操作数右边,所以单一地使用其中一种不容易发生混淆。 值得注意的是c++检查指针更为严格,指向多维数组的指针和该数据的同维指针在c++中是不同的,不能相互赋值。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

岚花落_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值