07. 指针、数组与引用

指针

void*​ 不允许加减,因为不知道指向对象的大小。

指针的数值运算(+​、-​、++​、--​),依赖于所指定的对象。

指针之间的减法只有指向同一数组的元素才有效,当计算两个指针 p​ 和 q​ 的差值(q - p​)时,所得结果是序列 [p:q)​ 中的元素数量(一个整数):

#include <iostream>
#include <cstddef> // For size_t

// 模板函数,计算两个指针之间的字节差
template<typename T>
size_t byte_diff(T* p, T* q) {
    return reinterpret_cast<char*>(q) - reinterpret_cast<char*>(p);
}

void diff_test() {
    int vi[10];
    short vs[10];

    std::cout << &vi[0] << " " << &vi[1] << " " << &vi[1] - &vi[0] << " " << byte_diff(&vi[0], &vi[1]) << "\n";
    std::cout << &vs[0] << " " << &vs[1] << " " << &vs[1] - &vs[0] << " " << byte_diff(&vs[0], &vs[1]) << "\n";
}

int main() {
    diff_test();
    return 0;
}

运行结果:

000000FBBC0FF948 000000FBBC0FF94C 1 4
000000FBBC0FF988 000000FBBC0FF98A 1 2

指针之间不允许加法,也没意义。

数组

不允许拷贝数组

int a1[10]{};
int a2[10] = a1; // 不OKa

不要尝试按值传递数组给函数。这其实并不是按值的语义,实际上也会影响原数组。此外,还会错误估计数组的大小。用传入指针(数组名会默认转换成指针)和大小代替。

#include <iostream>

void func(int arg[5]) {
    for (int i = 0; i < 5; ++i) {
        arg[i] += 10;
    }
}

int main() {
    int a1[10]{};

    // 并不是期待的按值传递,原数组被修改
    func(a1);

    for (int i = 0; i < 10; ++i) {
        std::cout << "a1[" << i << "] = " << a1[i] << std::endl;
    }
}

多维数组

#include <iostream>
int main() {
    int ma[3][5] = { {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10 }, {11, 12, 13, 14, 15} }; // 实际上,就是一个连续的空间

    for (int i = 0; i < 15; i++) {
        std::cout << *(&ma[0][0]+i) << " ";
    }
    std::cout << std::endl;

    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 5; j++) {
            std::cout << ma[i][j] << " ";
        }
        std::cout << std::endl;
    }

    for (int i = 0; i < 3; i++) {
        std::cout << ma[i] << " "; // 注意,ma[i] 会得到指针的结果,两两地址差值是 5*sizeof(int)
    }
    std::cout << std::endl;

    std::cout << ma[1] - ma[0] << std::endl; // 5

    return 0;
}

多维数组可以省略第一维:

#include <iostream>
int main() {
    int ma1[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // OK

    int ma2[][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // OK,省略第一维的大小

    int ma3[][3][3] = { { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }, { {10, 11, 12}, {13, 14, 15}, {16, 17, 18} } }; // OK,省略第一维的大小

    //int ma4[][][3] = { { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }, { {10, 11, 12}, {13, 14, 15}, {16, 17, 18} } }; // 不OK,第二维的大小不能省略
}

字符串

原始字符串:

#include <iostream>
#include <string>

int main() {
    // 使用原始字符串字面量包含双引号
    std::string str = R"(He said, "Hello, World!")";
    std::cout << "1. String with quotes: " << str << std::endl;

    // 使用原始字符串字面量包含反斜杠
    std::string path = R"(C:\Users\Name\Documents\file.txt)";
    std::cout << "2. Path with backslashes: " << path << std::endl;

    // 使用原始字符串字面量包含多行文本
    std::string multiLineStr = R"(
        #include <iostream>
        int main() {
            std::cout << "Hello, World!" << std::endl;
            return 0;
        }
    )";
    std::cout << "3. Multi-line string: " << std::endl << multiLineStr << std::endl;

    // 使用原始字符串字面量包含JSON字符串
    std::string jsonStr = R"({
        "name": "John",
        "age": 30,
        "city": "New York"
    })";
    std::cout << "4. JSON string: " << std::endl << jsonStr << std::endl;

    // 使用不同的定界符
    std::string customDelimiter = R"##(Custom delimiter ##)##";
    std::cout << "5. String with custom delimiter: " << customDelimiter << std::endl;

    return 0;
}

注意,原始字符串中的空格、制表符、回车等都被当作字符串的一部分了。

指针与 const

C++ 提供了两种与“常量”有关的概念:

  • constexpr​:编译时求值
  • const​:在当前作用域内,值不发生改变

一个指针牵扯到两个对象:指针本身以及指针所指的对象。在指针的声明语句中,“前置” const​ 关键字将令所指的对象而非指针本身成为常量。要想令指针本身成为常量,应该用 *const​ 代替普通的 *​。

const int* ptr;  // 指针所指的对象是常量
int* const ptr;  // 指针本身是常量

引用

和指针类似,引用作为对象的别名存放的也是对象的机器地址。与指针相比,引用不会带来额外的开销(有时候编译器能对引用进行优化,使得在运行时无须任何对象表示引用)。引用与指针的区别主要包括:

  • 访问引用与访问对象本身从语法形式上看是一样的。
  • 引用所引的永远是一开始初始化的那个对象。
  • 不存在“空引用”,我们可以认为引用一定对应着某个对象。

左值引用和右值引用

为了体现左值/右值以及 const/非 const 的区别,存在三种形式的引用:

  • 左值引用(lvalue reference​):引用那些我们希望改变值的对象

    • T&​:初始值必须是 T 类型的左值
  • const​ 引用(const reference​):引用那些我们不希望改变值的对象(比如常量)

    • const T&​:初始值不一定非得是左值,甚至可以不是 T​ 类型(其实是发生了隐式转换之后得到的右值,此时这个临时变量的声明周期会随着引用的结束而结束)
  • “右值引用”(rvalue reference​):所引对象的值在我们使用之后就无须保留了(比如临时变量)

    • T&&​:必须是右值
    • “破坏性读取”——使用移动(std::move​)来优化性能
#include <iostream>

int f() {
    return 0;
}

int main() {
    int i = 1;

    int& i_lvalue_ref = i;
    const int& i_const_ref = i;
    //int&& i_rvalue_ref = i; //不不OK,不是右值

    //int& j_lvalue_ref{ 1 }; //不OK,不是左值
    const int& j_const_ref{ 1 }; //OK
    int&& j_rvalue_ref{ 1 }; //OK

    //int& k_lvalue_ref{ f()}; //不OK,不是左值
    const int& k_const_ref{ f() }; //OK
    int&& k_rvalue_ref{ f() }; //OK
}

引用的引用

引用的引用,其实还是引用,只是可能是左值引用还是右值引用的区别:

using rr_i = int&&;
using lr_i = int&;
using rr_rr_i = rr_i&&; // "int&& &&" 的类型是 int&&
using lr_rr_i = rr_i&; // "int&& &" 的类型是 int&
using rr_lr_i = lr_i&&; // "int& &&" 的类型是 int&
using lr_lr_i = lr_i&; // "int& &" 的类型别名是 int&

总之,永远是左值引用优先(有一方是左值,就是左值)。

C++ 不允许下面的语法形式:

int&& & r = i;

引用的引用只能作为别名的结果或者模板类型的参数。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇