C++指针

 ***接上一篇博客:C++三目运算、函数和数据类型的转换及举例***

十八、指针

18.1 指针的基本概念

1) 变量的地址
变量是内存变量的简称,在C++中,每定义一个变量,系统就会给变量分配一块内存,内存是有地址的。
C++用运算符 & 获取变量在内存中的起始地址。
语法: &变量名;

2) 指针变量
指针变量简称指针,它是一种特殊的变量,专用于存放变量在内存中的起始地址
语法: 数据类型* 变量名;
数据类型必须是合法的C++数据类型 (int、char、double 或其它自定义的数据类型)。
星号*与乘法中使用的星号是相同的,但是,在这个场景中,星号用于表示这个变量是指针。

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

3) 对指针赋值
不管是整型、浮点型、字符型,还是其他的数据类型的变量,它的地址都是一个十六进制数。我们用整型指针存放整数型变量的地址; 用字符型指针存放字符型变量的地址;用浮点型指针存放浮点型变量的地址,用自定义数据类型指针存放自定义数据类型变量的地址。

4) 指针占用的内存
指针也是变量,是变量就要占用内存空间。
在64 位的操作系统中,不管是什么类型的指针,占用的内存都是 8 字节

18.2 使用指针

声明指针变量后,在没有赋值之前,里面是乱七八糟的值,这时候不能使用指针。
指针存放变量的地址,因此,指针名表示的是地址(就像变量名可以表示变量一样)
* 运算符被称为间接值或解除引用(解引用)运算符,解引用是指通过指针获取其所指向的值或对象,解引用操作符 "*" 放在指针变量前面,用于访问指针所指向的对象,将它用于指针,可以得到该地址的内存中存储的值,*也是乘法符号,C++根据上下文来确定所指的是乘法还是解引用。

程序在存储数据的时候,必须跟踪三种基本属性:
1、数据存储在哪里:
2、数据是什么类型;
3、数据的值是多少。

用两种策略可以达到以上目的:
声明一个普通变量,声明时指出数据类型和变量名(符号名),系统在内部跟踪该内存单元。
声明一个指针变量,存储的值是地址,而不是值本身,程序直接访问该内存单元。


18.3 指针用于函数的参数

如果把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址,在函数中通过解引引用的方法直接操作内存中的数据,可以修改实数的值,这种方法被通俗的称为地址传递或传地址。
值传递: 函数的形参是普通变量

传地址的意义如下:
1、可以在函数中修改实参的值
2、减少内存拷贝,提升性能。

18.4 用 const 修饰指针

1) 常量指针
语法:const 数据类型*变量名
不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
注意:
1、指向的变量(对象)可以改变 (之前是指向变量a的,后来可以改为指向变量 b)。
2、一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值。
3、如果用于形参,虽然指向的对象可以改变,但这么做没有任何意义。
4、如果形参的值不需要改变,建议加上const 修饰,程序可读性更好。

int a = 10;
int b = 20;
const int * const p = &a; // p指向a的地址,p的值不可改变,但p所指向的对象的值也不能改变
*p = 30; // 编译错误,不能修改p所指向的对象的值
p = &b; // 可以修改p所指向的对象的地址
cout << a << endl; // 输出10
cout << b << endl; // 输出20

2) 指针常量
语法:数据类型* const 变量名
指向的变量(对象)不可改变。
注意:
1、在定义的同时必须初始化,否则没有意义。
2、可以通过解引用的方法修改内存地址中的值。

int a = 10;
const int *p = &a; // p指向a的地址,p的值不可改变,但可以改变a的值
*p = 20; // 修改a的值为20
cout << a << endl; // 输出20

3) 常指针常量
语法:const 数据类型 * const 变量名
指向的变量(对象)不可改变,不能通过解引用的方法修改内存地址中的值。

常量指针:    指针指向可以改,指针指向的值不可以更改。
指针常量:    指针指向不可以改,指针指向的值可以更改。
常指针常量:指针指向不可以改,指针指向的值不可以更改。

18.5 void 关键字

在C++中,void 表示为无类型,主要有三个用途:
1、函数的返回值用 void,表示函数没有返回值
2、函数的参数填void,表示函数不需要参数(或者让参数列表空着)
3、函数的形参用 void*,表示接受任意数据类型的指针

注意:
·不能用 void 声明变量,它不能代表一个真实的变量。
·不能对 void*指针直接解引用(需要转换成其它类型的指针)。
·把其它类型的指针赋值给 void*指针不需要转换。
·把 void指针赋值给把其它类型的指针需要转换。

在C++中,使用cout输出一个字符指针时,如果该指针指向的内存位置没有以字符串的形式结束(即没有以“\0”尾),cout将会继续向后输出内存中的内容,直到遇到"\0"为止。因此,当输出一个char类型的变量的地址时,如果该地址后面的内存块没有以“\0”结束,cout会一直输出内存中的内容,这些内容可能是其他变量的值,可能是垃圾值,因此导致输出的是乱码。如果想输出char类型变量的地址,可以使用强制类型转换将其转换为void指针类型。

18.6 二级指针    

指针是指针变量的简称,也是变量,是变量就有地址。
指针用于存放普通变量的地址。
二级指针用于存放指针变量的地址。
声明二级指针的语法:数据类型** 指针名;
使用指针有两个目的: 1)传递地址,2)存放动态分配的内存的地址
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址的地址,形参用二级指针。

#include <iostream>
using namespace std;

// 定义一个结构体
struct Node {
    int data;
    Node *next;
};

int main() {
    Node *head = nullptr; // 头节点指针
    Node **p = &head; // 指向头节点指针的指针,即二级指针
    *p = new Node{1, nullptr}; // 在堆上分配一个新的节点,并将其地址赋给头节点指针
    (*p)->next = new Node{2, nullptr}; // 在堆上分配一个新的节点,并将其地址赋给新节点的next指针
    (*p)->next->next = new Node{3, nullptr}; // 在堆上分配一个新的节点,并将其地址赋给新节点的next指针所指向的节点的next指针

    Node *cur = head; // 当前节点指针
    while (cur != nullptr) { // 遍历链表
        cout << cur->data << " ";
        cur = cur->next;
    }
    cout << endl;

    // 释放内存
    delete (*p)->next->next;
    delete (*p)->next;
    delete *p;

    return 0;
}

 

18.7 空指针

在C和 C++中,用0或 NULL 都可以表示空指针。
声明指针后,在赋值之前,让它指向空,表示没有指向任何地址。
1) 使用空指针的后果
如果对空指针解引用,程序会崩溃
如果对空指针使用 delete 运算符,系统将忽略该操作,不会出现异常。
所以,内存被释放后,也应该把指针指向空。

为什么空指针访问会出现异常?
NULL指针分配的分区: 其范围是从 0x00000000到 0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面 NULL 指针分区。

2) C++11的nullptr
用0和NULL表示空指针会产生歧义,C++11建议用 nullptr 表示空指针,也就是(void*)0。
NULL在C++中就是0,这是因为在C++中void*类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议用 nullptr 替代NULL吧,而NULL就当做0使用。
注意:在 Linux 平台下,如果使用 nullptr,编译需要加-std=c++11 参数

18.8 野指针

野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。

*出现野指针的情况主要有三种:
1)指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)
2)如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是指向的地址已失效
3)指针指向的变量已超越变量作用域(变量的内存空间已被系统回收)。

规避方法:
1)指针在定义的时候,如果没地方指,就初始化为 nullptr
2)动态分配的内存被释放后,将其置为 nullptr
3)函数不要返回局部变量的地址。
注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度。

18.9 函数指针

它指向一个函数而不是一个对象。函数指针的主要用途是允许我们将函数作为参数传递给其他函数,或者将函数作为返回值返回。如果把函数的地址作为参数,就可以在函数中灵活的调用其它函数。

使用函数指针的三个步骤:
a)声明函数指针;b)让函数指针指向函数的地址;c)通过函数指针调用函数。
声明普通指针时,必须提供指针的类型。同样,声明函数指针时,也必须提供函数类型,函数的类型是指返回值和参数列表(函数名和形参名不是)

#include <iostream>
using namespace std;

// 定义一个函数,用于计算两个整数的和
int add(int a, int b) {
    return a + b;
}

// 定义一个函数,用于计算两个整数的差
int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 定义一个函数指针变量
    int (*func_ptr)(int, int);

    // 将函数指针指向add函数
    func_ptr = add;

    // 使用函数指针调用add函数
    int result = func_ptr(3, 4);
    cout << "The result is: " << result << endl;

    // 将函数指针指向subtract函数
    func_ptr = subtract;

    // 使用函数指针调用subtract函数
    result = func_ptr(7, 2);
    cout << "The result is: " << result << endl;

    return 0;
}

本节完。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

万叶学编程

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

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

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

打赏作者

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

抵扣说明:

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

余额充值