C++

重学C++系列(四)指针、引用

Posted by YaPi on January 18, 2022

内存单元

  • 内存由很多内存单元组成,这些内存单元用于存放各种类型的数据
  • 计算机对内存的每个内存单元都进行了编号,这个编号就称为内存地址,地址决定了内存单元在内存中的位置
  • 地址操作记忆不方便,所以c++编译器提供按名字来访问这些内存位置

指针基础概念

指针定义的基本形式:指针本身就是一个变量,其符合变量定义的基本形式,它存储的是值的地址。对类型T,T* 是 到T的指针类型,一个类型为T*的变量能保存一个类型T的对象地址。

  • 号靠近变量和靠近类型是没有区别的
int a = 112; float c = 3.14;
int* d = &a; float* e = &c;

通过一个指针访问它所指向地址的过程称为间接访问或引用指针;用于执行间接访问的操作服是单目操作服(*)

cout << (*d) << endl;
cout << (*e) << endl;

变量:

  1. 三个重要的信息 1.1 变量的地址位置 1.2 变量所存的信息 1.2 变量的类型

  2. 指针变量是一个专门用来记录变量的地址的变量;通过指针变量可以间接的操作另一个变量的值;

数组与指针

int main() {
    char strHelloWorld[] = {"hello"};
    char* pStrHelloWorld = {"hello"};
    // 指针变量的值允许改变
    pStrHelloWorld = strHelloWorld;
    // 数组变量的值不允许改变 报错
    // strHelloWorld = pStrHelloWorld;

    // strHelloWorld 不可变,strHelloWorld[index]的值可变
    // pStrHelloWorld可变,pStrHelloWorld[index]的值可变取觉于所值区间的存储区域是否可变

    return 0;
}

指针的数据 T* t[],数组中每一个元素都是一个指针 数组的指针 T(*t) [],指向一个数组的指针

int main() {
    int c[4] = {15,25,35,45};
    // 定义一个指针数组,每个元素都是一个指针
    int* a[4];
    // 定义一个指针,这个指针指向类型为int[4]的一个数组
    int(*b)[4];
    b = &c;
    for (unsigned i=0; i<4;i++){
        a[i] = &(c[i]);
    }
    cout << *(a[0]) << endl; // 15
    cout << (*b)[3] << endl; // 45
    return 0;
}

当需要使用指针来描述一个数组时,直接使用 数组类型 * 变量名来定义,如int类型则为 int * p。 同时指针变量p直接可看作数组c变量的别名进行操作。

int main() {
    int c[4] = {15,25,35,45};
    int * p;
    int * q;
    p = c;
    q = &(c[2]);
    cout << "&c = " << &c << endl;// 0x7ff7b3a378b0
    cout << "p = " << p << endl; // 0x7ff7b3a378b0,指针变量名为对应数组第一个元素的地址
    cout << "*p = " << *p << endl;// 指向数组第一个元素 15
    cout << "p[1] = " << p[1] << endl; // 25,直接类似数组操作[index]
    cout << "p+1 = " << p+1 << endl; // 地址 0x7ff7b3a378b4
    cout << "*(p+1) = " << *(p+1) << endl; // 25 加 1 则等同于数组操作[index+1],取后一个数组位置的值
    cout << "q = " << q << endl;// 多了两个int元素,则 加 8,地址为0x7ff7b3a378b8
    cout << "*q = " << *q << endl;// 35
    cout << "*(q+1) = " << *(q+1) << endl;// 45
    return 0;
}

定义:由四个指向int的指针组成的数组

int sum(int *ar2[4],int size);

定义:指向一个由4个int组成的数组的指针

int sum(int (*ar2)[4],int size);

const pointer 与 pointer to const

int main() {
    // const修饰看左边,左边没有再看右边
    char strHelloWorld[] = {"helloworld strHelloWorld"};
    // const 修改char,原数据不允许修改,但是指针的值可以修改
    char const* pStr1 = "hellowrold pStr1"; // const char
    // const 修改指针,指针的值可以修改
    char* const pStr2 = strHelloWorld;
    // 原始值和指针值都不允许修改
    char const* const pStr3 = "helloworld pStr3"; // const char* const
    // 不可改变
    // pStr2 = strHelloWorld;
    // 不可改变
    // pStr3 = strHelloWorld;

    for (unsigned index=0;index < strlen(pStr2); index++){
        // 不允许改变
        // pStr3[index] += 1;
        // 不允许改变
        // pStr1[index] += 1;
        pStr2[index] += 1;
    }

    cout << pStr2 << endl;
    cout << pStr1 << endl;
    cout << pStr3 << endl;
    cout << strHelloWorld << endl;
    return 0;
}

指向指针的指针

二级指针

int main() {
    int a = 123;
    int* b = &a;
    int** c = &b;
    // *操作符具有从右到左的结合性
    // **这个表达式相当于*(*c),必须从里向外层求值
    // *c得到的是c指向的位置,即b;b里面存储的是a的地址
    // **c相当于*b,得到变量a的值
    return 0;
}

野指针

指向垃圾内存的指针,if等判断对它们不起作用,因为没有设置为NULL

一般有三种情况

  1. 指针变量没有初始化
  2. 已经释放不用的指针没有置为NULL,如delete和free之后的指针
  3. 指针操作超越了变量的作用范围
int *a;
*a = 12;

指针a未初始化,会随机指向一个地址。若此时赋值

  1. 可能定位到一个非法地址,程序报错,从而终止
  2. 定位到一个可以访问的地址,无意中修改了值,引发不可预料的错误

所以,在指针进行访问之前,需要确保其已初始化,并被恰当的赋值

NULL指针一个特殊的指针变量,表示不指向任何东西。

int *a = NULL:

对于一个指针,若初始化需赋值为某一地址,否则需要设置为NULL.

对某一指针进行引用之前,需判断其值是否为NULL.

int *pA = NULL:

// 引用前判断NULL
if (pA != NULL){
    xxx
}
// 不用时设置为NULL
pA = NULL:

指针的操作

& :取地址符号

  • :取该地址对应的值

将变量名称看成地址

#include <iostream>
using namespace std;

int main() {
    int a = 1;
    // 将指针a1的值,设置为变量a的地址
    int *a1 = &a;
    // 查看a的值  1
    cout << "a : "<< a  << endl;
    // 查看a的地址 0x7ff7b5d0f8c8
    cout << "&a : "<< &a << endl;
    // a1的值,指向内存地址值 0x7ff7b5d0f8c8
    cout << "a1 : "<< a1 << endl;
    // 查看指针a1指向内存的值 1
    cout << "*a1 : "<< *a1 << endl;
    // 查看指针a1的值,指针的地址值 0x7ff7b5d0f8c0
    cout << "&a1 : "<< &a1 << endl;

    // 报错 a1 是指针的值,只能赋值为地址
    // a1 = 2;
    // 修改值
    int b = 2;
    // 将指针a1的值,设置为修改为b的地址
    a1 = &b;
    // 查看a的值 不变 1
    cout << "a : "<< a  << endl;
    // 查看a的地址 不变 0x7ff7b5d0f8c8
    cout << "&a : "<< &a << endl;
    // a1的值 变为b的地址 0x7ff7b5d0f8bc
    cout << "a1 : "<< a1 << endl;
    // 查看指针a1指向内存的值 变为2
    cout << "*a1 : "<< *a1 << endl;
    // 查看指针a1的地址 不变 0x7ff7b5d0f8c0
    cout << "&a1 : "<< &a1 << endl;

    // 将指针a1的值,指向的真实的内存的值修改为字符 'c'
    *a1 = 3;

    // 查看a的值 不变 1
    cout << "a : "<< a  << endl;
    // 查看a的地址 不变 0x7ff7b5d0f8c8
    cout << "&a : "<< &a << endl;
    // a1的值还是b的地址 0x7ff7b5d0f8bc
    cout << "a1 : "<< a1 << endl;
    // 查看指针a1指向内存的值 3
    cout << "*a1 : "<< *a1 << endl;
    // 查看指针a1的地址 0x7ff7b5d0f8c0
    cout << "&a1 : "<< &a1 << endl;
    // b的值 变为 3
    cout << "b : "<< b << endl;
    // b的地址值 不变 0x7ff7b5d0f8bc
    cout << "&b : "<< &b << endl;
    return 0;
}
++ 和 – 操作符

++ 操作

// 先进行加1 再执行运算,cp2是cp加1后的结果
char *cp2 = ++cp;

// 先进行运算,再进行加1,cp3是cp的值
char *cp3 = cp++;

– 操作和 ++ 操作类似。

++++,—-等运算符

编译器程序分解成符号的方法是:一个字符一个字符的读入,如果该字符可能 组成一个符号,那么读入下一个字符,一直到读入的字符不再能组成一个有意义的符号。

int a=1,b=2,c,d;
c = a+++b; // 相当于 a++ + b
d = a++++b; // 报错