第二章下

2024-7-13
这里特别是关于多文件的const在C++ primer中简单的带过去了,实际上理解起来需要用到其他像编译、链接的知识。

特点:

  • 限定变量只读,因此使用时必须初始化
  • 在多文件中共享const对象,该变量的定义和声明都添加extern关键字。对于const的实现,实际上是由编译器在编译过程中将const变量替换成对应的值,因此在多文件中,如果共享一个const对象,由于const对象都需要进行初始化,将造成重复定义的问题。
// file_1.cpp 定义并初始化了一个常量,该常量能被其他文件访问
extern const int a = GetValue();
// file_1.h 头文件中声明常量 a
extern const int a; // 此时的 a 与 file_1.cpp 中定义的 a 就是同一个

这个时候自然考虑到只在一个文件进行定义,在其他文件进行声明,思考以下例子1

//a.cpp
#include <iostream>
extern const int i;
int main() {
std::cout<<i;
return 0;
}
//b.cpp
const int i;

这里会报错未定义 i 。

实际上关键在于了解extern关键字的作用:改变const变量的默认链接属性

//fileA.cpp
extern const int i = 42; // 定义变量i并修改默认链接属性为全局可见
//fileB.cpp
extern const int i; // 声明这个变量在其他文件进行了定义

在例子1中,a.cpp中声明了 a 定义在其他文件中,然而b.cpp中对 i 的定义对a.cpp是不可见的。

但我们还要考虑编译顺序的影响,考虑以下例子2

//1.cpp
extern const int i; //const int i = 1;
const int i = 1; //extern const int i;
int main(){return 0;}
//2.cpp
extern const int i = 2;

只有按1.cpp中注释的写法能通过链接

按照编译的顺序检查文件,先 extern const int i,这时编译器会找这句之前是否对 i 进行了初始化,如果没有,则通过 extern 去文件外部,在 2.cpp 寻找到了对 i 的定义。这时,i 已经完成了定义。回到1.cpp后,发现了 const int i = 1,这时就会报错,重复定义:one or more multiply defined symbols found。按注释顺序,当extern const int i 时,已经在这个文件发现了对 i 的定义,不需要外部链接。
这里关于编译的知识还需要补一下

20240715

对const的引用(常量引用)

这时,引用也被视作常量,对const常量只能使用常量引用

const int i = 1;
const int& cri = i;
int& ri = i;//错误,类型不匹配

与普通引用不同的是,

  • 常量引用允许类型不匹配或者使用表达式作为初始值,其遵循类型转换规则。
const double b = 1.2;
const int& rb1 = b;
const double& rb2 = b*2;
std::cout<<rb1;//输出1
std::cout<<rb2;//输出2.4

特别的,能够为常量引用绑定一个非常量对象,这意味着不能通过该引用改变那个对象的值。

double b = 1.2;
const double& rb = b;
std::cout<<rb;//输出1.2
b = 2.2;
std::cout<<rb;//输出2.2

指针与const

  1. 指向const的指针

这里的规则与上述类似,但是不允许类型不匹配的指针

const double i = 1;
const double* cpi = &i;
double* pi = &i;//错误,类型不匹配
const int* qi = &i;//错误
double b = 1.2;
const double* pb = &b;
std::cout<<*pb;//输出1.2
b = 2.2;
std::cout<<*pb; //输出2.2
  1. const指针(常量指针)

    与引用不同的是,指针本身就是对象,因此可以把指针本身定义为常量。这就表示指针将一直指向该对象,指针值不能改变

    int a = 0;
    int b = 1;
    int* const p =&a;//p将一直指向a
    p = &b;//错误

    依然还是从右往左读判断声明。第一个const说明p本身是一个常量,不可修改其值,接下来的*表示p是一个指针,最后的int确定常量指针p指向了一个int对象。

    顶层const、顶层const与拷贝

一般来说,其实只有底层const。而对于指针这种,本身是一个对象,又指向一个对象,才会用到顶层const和底层const的概念,。而引用本身不是对象,因此只有底层const

  • 顶层const:表示指针本身是const

  • 底层const:指针所指的对象是const

还是从右往左读

const int* a;//a是一个指针,指针指向的对象是常量int,因此不能修改指向的内容(*a),但是可以修改a的值
int const* a;//同上
int* const a;//a是一个常量,a是一个常量指针,指针指向一个int。因此不能修改其值,但是可以修改指向的内容
const int* const a;//都不能修改
int const * const a;//都不能修改

简单来说,const默认作用于其左边的内容,否则作用于右边的东西。
在拷贝操作中,两个对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够进行转换(非常量可以转常量)

20240716

constexpr和常量表达式

常量表达式:值不会改变且在编译过程中就能得到计算结果的表达式或对象,这由它的数据类型和初始值共同决定。

将变量声明为constexpr类型意味这个它的值是常量表达式,编译器将会对其进行检查,可修饰普通变量、函数、类的构造函数。

constexpr与const的区别:

const声明的对象不一定是常量,而只是“只读”,比如对非常量对象进行的const引用和const指针

int a = 1;;
const int& ra = a;
const int* pa = &a;
std::cout<<ra<<" "<< *pa<<"\n";
a = 0;
std::cout<<ra<<" "<< *pa;

这就说明了const修饰的对象可以在运行期间进行修改,而constexpr是在编译期间进行求值。

  • constexpr指针:只对指针本身有效,而且必须初始化为nullptr、0、或存储于某个固定地址的对象,即必须是一个常量表达式
const int* pa = nullptr;//指向常量整型的指针
constexpr int* qa = nullptr;//指向整型的常量指针

处理类型

  1. 类型别名

    typedef double wages;//wages是double的同义词
    typedef wages base, *p;//base是double的同义词,p是double* 的同义词
    //特别注意不能进行简单的替换
    typedef char* pstring;//pstring的数据类型是指向char的指针
    const pstring cstr = 0;//指向char的常量指针,也就是说不能改变pstr的数据类型
    const char* cstr;//替换后变成了指向const char的指针
    const pstring *ps;//一个指针,其对象是指向char的常量指针
  2. 别名声明

    using wages = double;

auto类型说明符

由编译器自行推断数据类型,自然的,auto定义的变量必须有初始值。

auto会忽略顶层const,有需要时需要明确指出 const auto

不要滥用auto,Use auto to avoid redundant repetition of type names

decltype类型指示符

返回操作数的数据类型,在此过程中,编译器分析表达式并得到它的类型,但不实际计算其值。

decltype不会忽略顶层const

decltype与引用

这时必须进行初始化,特别由 decltype( ( variable ) )结果一定是引用

posted @   名字好难想zzz  阅读(5)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示