了解Const、指针、引用
本文最后更新于326 天前,其中的信息可能已经过时,如有错误请联系作者

了解Const、指针、引用

const

  1. 常量定义与替换
    • 使用 const 定义变量时,如果赋值的是一个立即数(字面量),则该变量是一个常量。编译器会在编译时将所有用到这个常量的地方替换为它的初始值,这称为常量折叠。若想在运行时改变这个值,需要使用 volatile 关键字,这会告诉编译器不要对这个变量进行优化。
    • 当以编译时初始化的方式定义一个const对象时,编译器将在编译过程中把用到该变量的地方都替换成对应的值。所以如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
  2. 成员函数的 const 修饰
    • const 用在成员函数的后面时,表示该成员函数不会修改类的任何成员变量。这被称为常量成员函数。实际上,它表示成员函数保证不会修改 this 指针指向的对象。
  3. 指针与 const
    • const 可以用来修饰指针,根据 const 位置的不同,分为顶层 const(指针常量)和底层 const(常量指针)。
    • 顶层 const:表示指针本身是一个常量,即不能改变指针的指向。并不意味着不能通过指针修改其所指对象的值
    • 底层 const:表示指针指向的是一个常量,即不能通过这个指针改变指向对象的值。没有规定其所指对象必须是一个常量,仅仅是不能通过该指针修改对象的值,也就是说对象的值可以通过别的途径改变。
    • 底层 const 指针不能赋值给非常量指针,因为这可能会允许通过非常量指针修改常量对象的值。
    • 二级const(指向常量的常量指针):指针本身和指向的内容都不能被修改。

需要注意的是顶层const可以表示任意的对象是常量,底层const则与指针和引用等复合类型部分有关。所有修饰引用的const都是底层const。

const int ci  = 42;//不能改变ci的值,这是一个顶层const
  1. 引用与const
  • 常量引用绑定非常量是合法的行为,但是不允许通过常量引用修改非常量
  • const引用本身不能被用作左值,即不能被赋值。这是因为const引用本身是常量,其值不能改变。
  • 注意不可以把引用绑到临时量
  1. constexpr

constexpr 变量

constexpr int max_size = 10;  // max_size 是一个编译期常量

这里,max_size 是一个 constexpr 变量,它的值在编译时就已经确定。

constexpr函数

constexpr int get_max_size() {
    return 10;  // 返回一个编译期常量
}

int main() {
    constexpr int size = get_max_size();  // size 也是一个编译期常量,这里的前提在于get_max_size()是常量表达式
}

get_max_size() 是一个 constexpr 函数,它的返回值在编译时就可以确定。因此,size 也是一个编译期常量。

constexpr对象

struct Point {
    constexpr Point(int x, int y) : x(x), y(y) {}
    int x, y;
};

constexpr Point p1(3, 4);  // p1 是一个编译期常量对象

必须注意一点,在constexpr声明中如果定义一个指针,限定符constexpr仅仅对指针有效,与指针所指的对象无关。

constexpr int *q= nullptr //这里的q是一个指针常量
constexpr int i = 42;
constexpr const int *p = &i; //这里的p是二级const

指针和引用

  1. 引用分为左值和右值还有万能引用
  2. 单说左值引用:

(1)引用可以说是更安全的指针,两者定义和修改时的汇编指令一样。

(2)指针的指向在非顶层const的情况是可变的,引用就像指针常量,不可修改指向。所以引用必须初始化,且不能为空,而指针可以不初始化和指向空。

(3)指针可以是多级的,引用只能一级

左值引用、右值引用:

先说一下左值,右值的区别:

左值是存储在内存中,有明确地址的数据,也就是说可以对其取地址。

右值不可取地址,但可以提供数据值的数据或者是将亡值。比如123这种常数或者是函数的值返回类型和move函数。

区分左值引用和右值引用,其实我们可以把右值引用当成左值引用的补丁:

左值引用的作用是:传参或者做函数返回值时,使用左值引用可以避免对象调用拷贝构造。

但是呢,左值引用有较大的局限性。

常见的比如:我们要vector扩容,要拷贝里面存着的十万个元素,或者接受一个较大的临时对象,以前的话虽然有深拷贝解决内存重复释放的问题,但肯定会消耗大量的拷贝时间,性能浪费,而且最主要的是拷贝完这些东西就不需要了。

所以右值引用就解决了这个问题,而且在STL的容器中,大量的出现了右值引用为参数的移动构造函数和其他函数,比如vector的push_back,emplace_back都能接受右值了,避免了多一次拷贝构造的开销,unique_ptr因为能拷贝右值而更方便了,最重要的是扩容时,大量的元素复制,而现在呢,只要我们的类中的移动构造函数声明为noexcept,这是因为vector扩容时要判断,如果写了移动构造并且是noexcept时,就会挨个调用移动构造,而不是拷贝构造,当对象里有很多的成员变量时,这样就大大的节省了性能开销。

Move这个函数其实非常有迷惑性,它并不是移动,仅仅是类型转换,其实底层的实现就是static_cast<&&>强制转换成右值。因为static cast在编译期间就完成了,所以运行时不会生成任何代码,连一个字节都不会生成。

而forward和move又很像,在普通情况下,两者的转换是一样的,都是强转为右值。但在函数模板且有引用折叠的时候,Move是强制把传入值转换为右值,而forward是根据传入参数是左值还是右值,返回其传入的类型。

两者都是先用remove_reference去除引用,怎么去除的呢,定义一个结构体模板,在里面重命名一下参数类型即可,为什么要去除引用呢,这是为了保证static_cast的双&&符号作用于非引用类型上,因为move和forward的模板都是万能引用,如果传入的参数恰好是一个左值引用,那么万能引用就会变成左值引用。

右值引用、移动语义、完美转发:

万能引用看起来和右值引用一样。给万能引用传左值时,就变成了左值引用,传右值变成右值引用。

什么时候不会成为万能引用呢?

但万能引用只在函数模板中,且发生了模板类型推断才会有,还不能加const限定符(volatile?)

暂无评论

发送评论 编辑评论


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