初始化器列表(通用)
- 确保不会发生导致信息丢失的转换(窄化类型转换)
-
int i1 = 7.2 // 变成7,信息丢失,意料之外? int i2 {7.2} // 报错,conversion from 'double' to 'int', possible loss of data int i2 = {7.2} // 报错,并且 = 多余
引入新名字时最好已经有了一个合适的值
- 常量声明时必须初始化
- 普通变量尽可能在声明时初始化
C++ 支持两种不变性概念
-
const:运行时和编译值都可,承诺不改变这个值
- 用于说明接口,传入函数不用担心被改变
-
constexpr:编译时求值(如果在编译时无法求值,会报错)
- 作用:作用是允许将数据置于只读内存中(不太可能被破坏)以及提升性能
-
const int dmv = 17; int var = 17; constexpr double max = 1.4 * dmv; // 可以,dmv 是常量 constexpr double max1 = 1.4 * var; // 不可以,var 是变量
- 如果函数在常量表达式中使用,则必须定义成
constexpr
,并且函数必须非常简单(即只能由一条用于计算某个值的 return 语句) -
constexpr
函数可以接收非常量实参,但结果不再是常量表达式 -
constexpr double square(double x) {return x*x;}
范围 for
#include <iostream>
int main() {
using namespace std;
int v[] = { 0,1,2,3,4,5,6,7,8,9 };
// 引用值
for (auto& x : v)
x *= 10;
// 拷贝值
for (auto x : v)
cout << x << '\n';
for (auto x : { 10, 21, 32, 43, 54, 65 })
cout << x << '\n';
}
类型
内置类型可由以下构造
- 声明运算符
&
*
[]
- 基本类型
- const 修饰符
与之对应的是用户自定义类型,如类和枚举等
用户自定义类型
-
结构体
struct
-
访问成员的两种方式
- 通过名字或引用:使用
.
访问 - 通过指针:使用
->
访问
- 通过名字或引用:使用
-
数据和操作分割(即数据 + 函数,函数接收数据作为参数)
-
-
类
class
- 数据和操作紧密联系
- 成员可见性可控
-
枚举
enum
- 普通
enum
,可隐式赋值给整型,但不可隐式被整型赋值 -
enum class
,不可隐式赋值给整型,不可隐式被整型赋值 -
#include <iostream> int main() { // 枚举值直接暴露出来 enum Color { RED, GREEN, BLUE }; // 枚举值在 ColorClass 作用域中 enum class ColorClass { YELLOW, ORANGE, PURPLE }; Color color = GREEN; int c1 = GREEN;// OK //Color c2 = 1;// 不OK ColorClass colorClass = ColorClass::ORANGE; //ColorClass colorClass = ORANGE; // 不OK //int c3 = ColorClass::ORANGE; // 不OK //ColorClass c4 = 1; // 不OK std::cout << "Color: " << color << std::endl; std::cout << "c1: " << c1 << std::endl; std::cout << "ColorClass: " << static_cast<int>(colorClass) << std::endl; }
- 普通
模块化
将接口和实现分离开
- 声明
- 定义
分离编译
声明和定义放置在分离的源文件里,并被分别编译
优点:编译时间缩短
class Vector {
public:
Vector(int s);
double& operator[](int i);
int size();
private:
double* elem; // elem指向一个数组,该数组包含sz个double
int sz;
};
#include "Vector.h" // 获得接口
Vector::Vector(int s) : elem(new double[s]), sz(s) {
// 构造函数的实现
}
double& Vector::operator[](int i) {
return elem[i]; // 下标操作符重载函数的实现
}
int Vector::size() {
return sz; // size函数的实现
}
名字空间 namespace
一方面表达某些声明是属于一个整体的,另一方面表明它们的名字不会与其他名字空间中的名字冲突。
namespace My_code {
class complex {/*...*/ };
complex sqrt(complex);
// ...
int main();
}
int My_code::main() {
complex z{ 1, 2 };
auto z2 = sqrt(z);
std::cout << '{' << z2.real() << ',' << z2.imag() << "}\n";
// .
};
// 真正的 main() 定义在全局名字空间中
int main() {
return My_code::main();
}
错误处理
异常
#include <iostream>
#include <stdexcept>
class Vector {
public:
// 构造函数
Vector(int s) : sz(s) {
elem = new double[sz];
}
// 析构函数
~Vector() {
delete[] elem;
}
// 重载下标运算符
double& operator[](int i) {
if (i < 0 || i >= sz) {
throw std::out_of_range("Index out of range");
}
return elem[i];
}
// 获取向量大小
int size() {
return sz;
}
private:
double* elem; // 指向double数组的指针
int sz; // 向量大小
};
int main() {
Vector v(5); // 创建一个大小为5的Vector
try {
v[10] = 3.14; // 尝试访问越界的元素
}
catch (const std::out_of_range& e) {
std::cerr << "Caught an exception: " << e.what() << std::endl;
}
return 0;
}
不变式
前置条件(即:索引值必须在[0:size())范围内)未满足,函数拒绝执行,并抛出异常
对于类来说,这样一条假定某事为真的声明称为类的不变式(class invariant) ,简称为不变式(invariant) 。
在这个例子中不变式是:elem 指向一个含有 sz 个 double 的数组
建立类的不变式是构造函数的任务(从而成员函数可以依赖于该不变式),它的另一个作用是确保当成员函数退出时不变式仍然成立。
Vector v(-27) // 会报错
修正后:
Vector(int s) : sz(s) {
if (s<0) throw std::length_error{};
elem = new double[sz];
}
静态断言
编译时处理,适用于任何可以表达为常量表达式的东西
int main() {
// OK
static_assert(4<=sizeof(int), "int is not an integral type.");
// 编译报错:error C2338: static_assert failed: 'int is not an integral type.'
static_assert(4>sizeof(int), "int is not an integral type.");
}