吉林大学面向对象编程笔记(4)指针、数组、引用、常量

156 阅读7分钟

指针、数组、引用、常量

指针

Type * varName [= 地址表达式];

int    num    = 5;
int * pNum  = #
int * pN2     = pNum;
void    *     p= 0;
bool    **   p = 0;
int       **   ppNum = &pNum;
int *  pName = NULL;     (C++98)
int *  pName = nullptr;   (C++1z)

指针的dereference

解引用(Dereference) :解引用是指使用指针来访问其所指向的内存位置上的值。在C++中,可以使用*运算符来进行解引用操作。例如,在代码*pa = b;中,*pa表示解引用指针pa,将变量b的值赋给pa所指向的内存位置。

间接引用(Indirection) :间接引用是指通过一个或多个指针来访问数据。在代码int **ppa = &pa;中,ppa是一个指向指针pa的指针,因此通过ppa间接引用,我们可以访问pa所指向的内存位置。

逆向引用(Reverse Reference) :逆向引用是指从指针追溯到其指向的对象或变量。在代码*ppa = &b;中,*ppa表示逆向引用指针ppa,将变量b的地址赋给ppa所指向的内存位置。

指针的运算涉及到指针的地址计算和偏移量的操作:

  • 在代码int *p = &a[0]; cout << *(++p);中,p指向数组a的第一个元素。通过++p操作,指针p向后移动一个位置,然后使用解引用操作符*访问指针p所指向的内存位置上的值。因此,*(++p)的结果是数组a的第二个元素的值,即2
  • 在指针运算中,当执行p + n时,指针p会增加n个偏移量。这等效于将指针p的地址转换为char*类型,然后通过增加sizeof(T) * n个字节来计算新的地址,最后将其转换回T*类型。
  • 取数组元素a[k]等价于*(a + k),即通过将指针a向后移动k个位置来访问数组元素,然后使用解引用操作符*来获取该位置上的值。

数组

一维数组

Type varName[ constexp ] [= {初值列表}];

(C++98):int a[5] = {1,2,3,4,5}; int* b=a; int* c=&a[0];

(C++1z新增):int a[5] { }; int* b=a; int* c=&a[0];

多维数组

(C++98) Type varName[ constexp1 ][constexp2]… [={初值列表}];

(C++1z) Type varName[ constexp1 ][constexp2]… [ {初值列表} ];

指针数组与数组指针

在C++中,指针数组和数组指针是两个不同的概念:

指针数组:指针数组是指一个数组,其中的每个元素都是指针类型。在定义指针数组时,需要使用指针类型后面加上方括号[]来表示这是一个数组。例如,int* array[5];表示一个包含5个元素的指针数组,每个元素都是指向int类型的指针。这意味着array[0]array[1]array[2]等都是指向int类型的指针。

数组指针:数组指针是指一个指针,它指向一个数组。在定义数组指针时,需要使用括号将指针类型和数组维度括起来,以明确指针所指向的数组类型。例如,int (*p)[5];表示一个指向包含5个int元素的数组的指针。这意味着p指向的是一个包含5个int元素的数组,而不是指向int类型的指针。

总结起来:

  • 指针数组是一个数组,其中的元素都是指针类型。
  • 数组指针是一个指针,它指向一个数组。

示例用法:

// 指针数组
int* array[5];  // 定义一个包含5个元素的指针数组
​
int a = 1, b = 2, c = 3, d = 4, e = 5;
array[0] = &a;  // 指针数组的第一个元素指向变量a
array[1] = &b;
array[2] = &c;
array[3] = &d;
array[4] = &e;
​
// 数组指针
int arr[5] = {1, 2, 3, 4, 5};
int (*p)[5];  // 定义一个指向包含5个int元素的数组的指针
p = &arr;  // 数组指针p指向数组arr

引用(左值引用)

<类型> & <变量>=<对象或变量>;

  • 是一个别名
  • 对应的变量/对象,必须存在
  • 必须初始化

指针和引用的差异

  • 存取值的方法
  • 初始化
  • 对象或变量的存在性
// 使用变量ab
int a = 7, b = 8;
​
// 函数定义,传值方式,不修改原变量ab
void f(int a, int b) {
  cout << a << b << endl;
}
​
// 调用方式:f(a, b);
// 这里的ab是函数f的局部变量,不会修改原变量ab的值
​
// 传指针方式,通过指针修改原变量ab的值
int a = 7, b = 8;
​
void f(int* pa, int* pb) {
  *pa = 1;
  *pb = 2;
}
​
// 调用方式:f(&a, &b);
// 通过传递指向变量ab的指针,函数f可以修改原变量ab的值
​
// 传引用方式,通过引用修改原变量ab的值
int a = 7, b = 8;
​
void f(int& a, int& b) {
  a = 1;
  b = 2;
}
​
// 调用方式:f(a, b);
// 通过传递变量ab的引用,函数f可以直接修改原变量ab的值
​

常量

  • 文字常量(字面常量)

    100, 3.14, true, ’C’, “吉林大学”,…

  • 宏定义

    #define VALUE 98

    缺点:

    无类型检查

    优点(有其它作用)

    • 可用于条件编译

    • 可使用#,##,#@

    • 可使用 LINE,FILE,FUNCTION

      • # 运算符:在宏定义中,# 运算符用于将宏参数转换为字符串常量。它在宏展开时对参数进行字符串化操作。例如:

        #define STR(x) #x
        ​
        cout << STR(Hello);  // 输出字符串 "Hello"
        
      • ## 运算符:在宏定义中,## 运算符用于将两个符号组合在一起,形成一个新的标识符。它在宏展开时进行标识符的连接。例如:

        #define CONCAT(x, y) x##y
        ​
        int ab = CONCAT(a, b);  // 等价于 int ab = ab;
        

        #@ 运算符:

        宏定义 #define TOCHAR(x) #@x,这是一个将给定的宏参数转换为字符的宏。在这个宏定义中,# 是字符串化运算符,用于将宏参数转换为一个字符序列。

      • __LINE____FILE____FUNCTION__:它们是预定义的宏,在编译过程中由编译器自动生成,用于获取代码的行号、文件名和函数名。

        • __LINE__ 宏用于获取当前代码行的行号。
        • __FILE__ 宏用于获取当前代码所在文件的文件名。
        • __FUNCTION__ 宏用于获取当前代码所在函数的函数名。

        这些宏在调试和错误处理时非常有用,可以用于打印错误信息、日志记录等操作。例如:

        cout << "Error at line " << __LINE__ << " in file " << __FILE__ << endl;
        

        上述代码将输出发生错误的代码行号和文件名。

  • 命名常量

    const int CARD_COUNT = 54;

  • const和指针

    两种形式等价

    • const int* pt = &a;
    • int const* pt = &a;

    指针和常量指针的声明和使用涉及到指向常量的指针和普通指针的转换。指向常量的指针(const int )可以指向常量和非常量的对象,而普通指针(int)只能指向非常量的对象。

    int v1 = 100;
    int v2 = 3;
    ​
    const int* p1 = &v1;  // 正确,p1指向v1
    cout << *p1 << endl;  // 输出100,通过p1解引用获取v1的值
    ​
    const int* p2 = &v2;  // 正确,p2指向v2
    ​
    int* p3 = &v1;  // 正确,p3指向v1
    int* p4 = p1;   // 错误,p1是指向常量的指针,不能将其赋值给普通的指针
    ​
    int v3 = *p1;   // 正确,通过p1解引用获取v1的值,并将其赋给变量v3
    ​
    const int cv1 = 200;
    const int cv2 = 4;
    ​
    const int* pc1 = &cv1;  // 正确,pc1指向cv1
    cout << *pc1 << endl;   // 输出200,通过pc1解引用获取cv1的值
    ​
    const int* pc2 = &cv2;  // 正确,pc2指向cv2
    ​
    int* p1 = pc1;  // 错误,pc1是指向常量的指针,不能将其赋值给普通的指针
    ​
    int v1 = cv1;   // 正确,将cv1的值赋给变量v1
    ​
    int v2 = *pc1;  // 正确,通过pc1解引用获取cv1的值,并将其赋给变量v2

    常指针

    // 普通指针
    T* p [= exp];
    // 使用普通指针p,它可以指向类型为T的对象,可以通过初始化表达式exp进行初始化
    ​
    // 指向常量的普通(non-const)指针
    const T* pt [= exp];
    // 使用指向常量的普通指针pt,它可以指向类型为const T的常量对象或非常量对象,可以通过初始化表达式exp进行初始化
    ​
    // 指向变量的常(const)指针
    T* const pt = exp;
    // 使用指向变量的常指针pt,它必须进行初始化,并且指向类型为T的变量,初始化表达式exp用于指定所指向的变量
    ​
    // 指向常量的常(const)指针
    const T* const pt = exp;
    // 使用指向常量的常指针pt,它必须进行初始化,并且指向类型为const T的常量对象或非常量对象,初始化表达式exp用于指定所指向的常量对象
    ​
    int var1 = 5;
    int var2 = 10;
    ​
    int* const pt1 = &var1;  // 正确,pt1是一个指向整型变量的常指针,指向var1
    pt1 = &var2;  // 错误,不允许修改pt1存放的值,即不允许指向其他变量
    *pt1 = var2;  // 正确,虽不能指向其他变量,但可以修改指向的内容
    ​
    const int* pt2 = &var1;
    *pt2 = var1;  // 错误,不能修改*pt2所指向的内容,因为pt2指向一个常量对象
    pt2 = &var2;  // 正确,pt2可以指向其他变量,因为它是一个指向常量的指针var2 = 99;  // 正确,只要不通过pt2指针,可以任意修改var2的值
    cout << *pt2;  // 输出99,此时*pt2的值是99
  • const和引用

    // 变量的引用
    int val = 100;
    int& myval1 = val;             // 引用变量myval1绑定到变量val
    const int& myval2 = val;       // 引用变量myval2绑定到常量valmyval1 = 300;                  // 通过myval1修改val的值为300,val的值变为300
    myval2 = 100;                  // 错误,myval2是一个引用到常量的引用,不能通过它修改变量val的值
    ​
    // 常量的引用
    int b = 100;
    int& aa = b;                   // 引用变量aa绑定到变量b
    const int& aa = b;             // 引用变量aa绑定到常量b
    const int& bb = 1;             // 引用变量bb绑定到常量1aa = 10;                       // 通过aa修改b的值为10,b的值变为10
    bb = 20;                       // 错误,bb是一个引用到常量的引用,不能通过它修改常量1的值
    ​
    int var = 100;
    const int cvar = 200;
    ​
    void f(int a1, int& a2, const int& a3) { }
    f(1, 2, 3);                   // 错误,无法将整型字面值绑定到非常量引用
    f(1, var, var);               // 正确,将变量var的引用传递给a2和a3
    f(1, cvar, cvar);             // 正确,将常量cvar的引用传递给a2和a3
    f(1, var, cvar);              // 正确,将变量var和常量cvar的引用传递给a2和a3
    f(1, cvar, var);              // 正确,将常量cvar和变量var的引用传递给a2和a3
    f(var, var, var);             // 正确,将变量var的引用传递给a2和a3
    f(cvar, var, cvar);           // 正确,将常量cvar的引用传递给a2和a3