Vars and Types
Vars and Types
声明和定义
一个变量可以多次声明,但只能定义一次
如果想要声明一个变量而非定义它,就在前面加关键字extern,而且不要显示地初始化变量。
包含了显式初始化的声明将自动变成定义(extern关键字失效)
- 如果局部变量与全局变量同名,可通过 extern 声明全局变量,覆盖局部作用域的同名变量。
- 全局变量默认具有外部链接性,但如果想在 A 文件中使用 B 文件定义的全局变量,必须用 extern 声明(否则编译器会认为变量未定义)
头文件里不要放定义,因为头文件可能会被到处include,导致定义多次出现而报错
多种初始化
extern int a;
extern int a;
int main(){
long long l = 31415926536;
int m = l; //警告
int n(l); //警告
// int k = { d }; //错误,更安全
// int p{l}; //错误,更安全
int i = 3;
long long t{i}; //OK
return 0;
}用{ }来初始化作为C++11的新标准一部分得到了全面应用,这种形式称为列表初始化。
重要特点:存在丢失信息的风险时,将报错。
const vs volatile
| 限定符 | 核心含义 | 核心目标 | 编译器行为 |
|---|---|---|---|
const | “只读”:修饰的对象逻辑上不可被当前代码修改(编译器强制检查) | 防止意外修改,提升代码安全性 | 1. 禁止代码直接修改该对象; 2. 允许编译器对其做优化(如缓存到寄存器); 3. 可通过指针强制突破(不推荐)。 |
volatile | “易变”:修饰的对象可能被当前代码之外的因素修改(如硬件 / 中断 / 其他线程) | 禁止编译器过度优化 | 1. 每次访问都直接读写内存(不缓存到寄存器); 2. 不优化掉 “看似无用” 的访问; 3. 不保证对象不可修改(反而允许外部修改)。 |
叠加使用 const 和。volatile | 我不能改,但数据会变 | 硬件编程高频用法 | 1. 拦截试图修改变量的操作; 2.每次访问都读取最新值 |
声明为
const的结构体对象,其内部的所有非静态成员(包括数据成员、成员函数)都会被隐式附加const属性,无法修改,且只能调用类中声明为const的成员函数,无法调用非const成员函数
左值和右值
C++中的左值与右值的区别在于是否可以寻址:可以寻址的对象(变量)是左值,不可以寻址的对象(变量)是右值。
左值持久、右值短暂。 左值具有持久的状态(取决于对象的生命周期),而右值要么是字面量,要么是在表达式求值过程中创建的临时对象。
在C语言里,左值可以出现在赋值语句的左侧(当然也可以在右侧),右值只能出现在赋值语句的右侧。
i++: 右值,++i: 左值
左值引用&
相当于给已经存在的对象起了个别名。
对引用进行的所有操作实际上都是作用在与之绑定的对象之上。被引用的实体必须是分配内存的实体(能按字节寻址)
- 寄存器变量可被引用,因其可被编译为分配内存的自动变量。
- 位段成员不能被引用,计算机没有按位编址,而是按字节编址。
- 引用变量不能被引用。
- 数组元素不能为引用类型
引用类型都要与绑定的对象严格匹配,except
- const引用可以引用非const变量(可以增加属性,反之不然),甚至可以引用右值。
- 为右值(如
10)创建一个临时的无名左值对象(比如const int temp = 10;); - 将
const T&绑定到这个临时对象,并延长其生命周期至引用失效。
- 为右值(如
- 父类引用绑定到子类对象
右值引用&&
必须绑定右值,相当于延长了临时对象的生命周期。和赋值相比,又避免了拷贝,提升效率。
constexpr
常量表达式:是指值不会改变而且在编译时可以求值的表达式,如字面量就是常量表达式,用常量表达式初始化的const变量。
可以将变量声明为constexpr类型以便由编译器检测变量的值是否为常量表达式(c++11)
constexpr int x=10;
constexpr int y=x;
int k=0;
// constexpr int z=k; // error: 未用常量初始化
return 0;定义指针时,constexpr仅对指针本身有效,对所指对象无效:
constexpr int *p=nullptr; //p是常量指针,指向整型变量constexpr 函数
函数返回类型和形参类型都必须是字面值类型
#include <iostream>
using namespace std;
constexpr int t=10;
constexpr int f(int x){
x++;
if(x>0){
return x*2;
} else {
return x+10;
}
}
int main(){
constexpr int x=f(1);
cout<<x<<endl;
cout<<f(x)<<endl;
int k=12;
cout<<f(k); // constexpr 函数也支持运行时调用
// constexpr y=f(k); // error 参数不是字面量
return 0;
}mutable
函数后加上const承诺不修改类的成员,而用mutable声明成员变量可以bypass这一限制
const对象的mutable成员也可以被修改
mutable只能用来定义类的数据成员
class A{
int x;
mutable int y;
void f() const{
// x++; //error
y++;
}
};new delete
new 分配内存并调用构造函数
delete 相当于 调用析构函数并free该对象的内存, delete[]会对数组中的每个元素调用析构函数
| 场景 | 正确delete | 错误delete | 后果(自定义类型) |
|---|---|---|---|
new T(单个对象) | delete | delete[] | 内存越界、崩溃 |
new T[N](数组) | delete[] | delete | 析构函数调用不全、资源泄漏 |
#include <iostream>
using namespace std;
struct A{
A(){
cout<<"A"<<endl;
}
~A() {
cout<<"~A"<<endl;
}
};
int main(){
A *a=new A[10]; // 执行10次构造函数
delete [] a; // 执行10次析构函数
return 0;
}类型转换
- 简单类型之间的强制类型转换的结果为右值
- 可写变量进行同类型的左值引用转换,则转换结果为左值
强制类型转换修改const变量
#include <iostream>
struct A{
const int v=10;
};
int main() {
const volatile int x=10; // 非volatile会被编译器优化,仍然是10
*(int*)&x=20;
std::cout<<x<<std::endl;
A a;
*(int*)&(a.v)=20;
std::cout<<a.v<<std::endl;
return 0;
}cast
static_cast同C语言的强制类型转换用法基本相同,但不能从源类型中去除const和volitale属性,不做多态相关的检查。const_cast同C语言的强制类型转换用法基本相同,但能从源类型中去除const和volitale属性。dynamic_cast主要用于有继承关系的基类型和派生类型之间的相互转换。将子类对象转换为父类对象时无须子类多态,而将基类对象转换为派生类对象时要求基类多态。reinterpret_cast通常为运算对象的位模式提供低层级的重新解释。主要用于名字(运算对象)同指针或引用类型之间的转换,以及指针与足够大的整数类型(能够存放地址)之间的转换。
引用异常抛出std::bad_cast
自动推导类型
auto
auto: 通过表达式的值判断变量类型
- auto 可以再加 static/const 修饰
- 函数也可以用const
- 类的成员不能用auto,因为在编译时没有对象
- 数组会被推断为指针
typeid(x).name 检查类型
decltype
decltype():返回操作数的类型(不会计算操作数的值)
所有写类型的地方都可以用decltype()代替
