宏
int main()
{
// 1. 预定义 __func__ 宏
std::cout << __func__ << std::endl;
// 2. 在 .h 文件中使用 _Pragma
// _Pragma ("once") // #pragma once 该头文件被编译一次
// 3. 变参宏 __VA_ARGS__
#define PRINT(...) printf(__VA_ARGS__);
PRINT("%d %s\n", 1, "HelloWorld");
return 0;
}
输出结果:
main
1 HelloWorld
- 对 C++98 标准定义的一些宏的定义与支持
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
int main()
{
return 0;
}
- 因为c编译器和c++编译器不同,所以在c++中调用c编译器编译出的库时,通过 __cplusplus 宏进行处理,extern “C” 避免了 c++编译器对其中函数名、变量名等符号的命名倾轧
- 添加了char16_t、char32_t、long long 等新定义类型
断言
#include <iostream>
#include <cassert> // assert 宏头文件
int main()
{
int n = 10;
assert(n > 10); // 此处断言运行时 n 一定大于10,否则中断程序
return 0;
}
输出结果:
Assertion failed!
Program: D:\PROJECT\Qt\c++\debug\day1.exe
File: …\main.cpp, Line 10 // main.cpp 文件的第十行报错
Expression: n > 10 // 断言 n > 10,实际 n <= 10
- c语言提供了 assert 宏进行运行时判断,对于程序调试比较方便
- c++11提供了静态类型断言(static_assert),在程序编译时可以做一些断言判断
#ifdef NDEBUG
#define assert(_Expression) ((void)0)
#else /* !defined (NDEBUG) */
...
- 分析实现源码可知,如果代码中定义了 NDEBUG 宏,则 assert 语句将不生效(无实际意义,可能汇编编译器优化)
int main()
{
// static_assert(常量表达式, 描述字符串)
// 断言表达式的结果必须在编译期间可计算出来(必须是常量表达式),否则语法报错
static_assert (12 > 10, "ERROR"); // OK
// static_assert (10 > 10, "ERROR"); // 编译报错,条件不满足
return 0;
}
noexcept
void Func() throw (int) { // 抛出 int 异常
throw -1;
}
void Func2() noexcept {
throw -1; // 此处强制抛出异常
}
void Func3() noexcept(false) { // noexcept(值可转换为 bool 的表达式),true 则不抛出异常, false 则抛出异常
throw -1;
}
int main()
{
try {
Func(); // 抛出异常
} catch (int e) {
std::cout << "e : " << e << std::endl;
}
try {
Func3();
} catch (int e) {
std::cout << "e : " << e << std::endl;
}
try {
Func2(); // 使用 noexcept 修饰函数,但例子中仍然抛出异常,这时调用 std::terminate 函数直接终止程序
} catch (int e) {
std::cout << "e : " << e << std::endl;
}
return 0;
}
输出结果:
e : -1
e : -1
terminate called after throwing an instance of ‘int’
- noexcept 修饰的函数不会抛出异常
- noexcept(表达式) 修饰的函数,如果表达式值为 true 不会抛出异常,表达式值为 false 会抛出异常
类成员初始化
class CBase {
public:
void Print() {
std::cout << "id : " << m_id << std::endl;
}
private:
int m_id = 10; // 定义并初始化
};
int main()
{
CBase base;
base.Print();
return 0;
}
输出结果:
id : 10
class CBase {
public:
CBase() : m_id(20) { // 初始化列表初始化
}
void Print() {
std::cout << "id : " << m_id << std::endl;
}
private:
int m_id;
};
int main()
{
CBase base;
base.Print();
return 0;
}
输出结果:
id : 20
class CBase {
public:
CBase() {
m_id = 30; // 构造函数中初始化
}
void Print() {
std::cout << "id : " << m_id << std::endl;
}
private:
int m_id;
};
int main()
{
CBase base;
base.Print();
return 0;
}
输出结果:
id : 30
// “直接”初始化和初始化列表初始化的优先级
class CBase {
public:
CBase() : m_id(20) {
}
void Print() {
std::cout << "id : " << m_id << std::endl;
}
private:
int m_id = 10;
};
int main()
{
CBase base;
base.Print();
return 0;
}
输出结果:
id : 20
// 初始化列表和在构造函数体中初始化的优先级
class CBase {
public:
CBase() : m_id(20) {
m_id = 30;
}
void Print() {
std::cout << "id : " << m_id << std::endl;
}
private:
int m_id = 10;
};
int main()
{
CBase base;
base.Print();
return 0;
}
输出结果:
id : 30
- 类成员初始化常见的三种方法是,定义并初始化,初始化列表初始化,在构造函数体中初始化
- 注意这三种初始化方式的优先级
sizeof
class CBase {
public:
int m_id = 10;
static int m_size;
};
int CBase::m_size = 2;
int main()
{
// 获得成员变量的大小
CBase base;
std::cout <<"sizeof (CBase::m_size) : " << sizeof (CBase::m_size) << std::endl;
std::cout <<"sizeof (base.m_id) : " << sizeof (base.m_id) << std::endl;
std::cout <<"sizeof (((CBase *)0)->m_id) : " << sizeof (((CBase *)0)->m_id) << std::endl;
std::cout <<"sizeof (CBase::m_id) : " << sizeof (CBase::m_id) << std::endl;
return 0;
}
输出结果:
sizeof (CBase::m_size) : 4
sizeof (base.m_id) : 4
sizeof (((CBase *)0)->m_id) : 4
sizeof (CBase::m_id) : 4
- c++98 标准只有对类静态成员变量或类实例化对象调用成员变量才能使用 sizeof,c++11 标准中可以直接对类的非静态成员变量使用 sizeof 运算符
friend改进
class CBase3; // 类前置声明
class CBase {
public:
void Print() {
std::cout << "CBase m_id : " << m_id << std::endl;
}
private:
int m_id = 10;
friend class CBase2; // 以前的写法
friend CBase3; // c++11改进
};
class CBase2 {
public:
void Print() { // 访问私有成员
std::cout << "CBase2 m_id : " << m_base.m_id << std::endl;
}
private:
CBase m_base;
};
class CBase3 {
public:
void Print() { // 访问私有成员
std::cout << "CBase3 m_id : " << m_base.m_id << std::endl;
}
private:
CBase m_base;
};
int main()
{
CBase base;
CBase2 base2;
CBase3 base3;
base.Print();
base2.Print();
base3.Print();
return 0;
}
输出结果:
CBase m_id : 10
CBase2 m_id : 10
CBase3 m_id : 10
- 声明友元时可以去掉 class 关键字,但注意需要找到类类型,必要时可使用类前置声明
- 一般测试用例可以使用友元声明,达到访问被测类私有成员变量的目的,减少代码量
template <typename T>
class CBase {
public:
void Print() {
std::cout << "CBase m_id : " << m_id << std::endl;
}
private:
int m_id = 10;
friend T; // 声明友元
};
class CBase2 {
public:
void Print(const CBase<CBase2> &base) { // 访问私有成员
std::cout << "CBase2 m_id : " << base.m_id << std::endl;
}
// void Print2(const CBase<int> &base) { // 访问私有成员失败
// std::cout << "CBase2 m_id : " << base.m_id << std::endl;
// }
};
int main()
{
CBase<int> base;
CBase<CBase2> base2;
base.Print();
base2.Print();
CBase2 base3;
// base3.Print(base); // 类型不匹配,报错
base3.Print(base2);
return 0;
}
输出结果:
CBase m_id : 10
CBase m_id : 10
CBase2 m_id : 10
final
// 未添加 final 关键字的基本继承模型
class CBase {
public:
virtual void Print() {
std::cout << "CBase Print : " << std::endl;
}
};
class CDerive : public CBase {
public:
virtual void Print() {
std::cout << "CDerive Print : " << std::endl;
}
};
int main()
{
return 0;
}
// 虚函数添加 final 关键字,中断子类对此虚函数的重写
class CBase {
public:
virtual void Print() final { // 添加 final 的虚函数不能在被子类重写
std::cout << "CBase Print : " << std::endl;
}
};
class CDerive : public CBase {
public:
// virtual void Print() { // 编译报错
// std::cout << "CDerive Print : " << std::endl;
// }
};
int main()
{
return 0;
}
// 类添加 final 关键字,表示此类被禁止继承
class CBase final { // 此类禁止被继承
public:
virtual void Print() {
std::cout << "CBase Print : " << std::endl;
}
};
//class CDerive : public CBase { // 编译报错
//public:
// virtual void Print() { // Error
// std::cout << "CDerive Print : " << std::endl;
// }
//};
int main()
{
return 0;
}
override
class CBase {
public:
virtual void Print() {
std::cout << "CBase Print : " << std::endl;
}
virtual void Print2() {
std::cout << "CBase Print2 : " << std::endl;
}
};
class CDerive : public CBase {
public:
virtual void Print() override { // 此处重写基类虚函数
std::cout << "CDerive Print : " << std::endl;
}
// override 标识的虚函数表示重写父类的虚函数,必须要有函数实现体,否则 编译不通过
virtual void Print2() override; // 未实现函数体
// void Print3() override { // 普通函数添加 override 标识报错
// std::cout << "CDerive Print : " << std::endl;
// }
};
int main()
{
CDerive d; // 此处编译报错
return 0;
}
- override 是派生类重写父类虚函数的标识,一旦派生类声明父类的虚函数添加此标识,则必须有函数实现体
- override 只用于修饰派生类重写基类的虚函数,普通函数直接编译报错
auto
#include <iostream>
#include <cxxabi.h>
int main()
{
int a = 10;
auto b = a;
const auto *p = &b;
static auto c = 10.0;
auto d = c;
// auto e; // 语法错误,此处无法推导类型
std::cout << "a type : " <<abi::__cxa_demangle(typeid (a).name(), 0, 0, 0) << std::endl;
std::cout << "b type : " <<abi::__cxa_demangle(typeid (b).name(), 0, 0, 0) << std::endl;
std::cout << "c type : " <<abi::__cxa_demangle(typeid (c).name(), 0, 0, 0) << std::endl;
std::cout << "d type : " <<abi::__cxa_demangle(typeid (d).name(), 0, 0, 0) << std::endl;
std::cout << "p type : " <<abi::__cxa_demangle(typeid (p).name(), 0, 0, 0) << std::endl;
return 0;
}
输出结果:
a type : int
b type : int
c type : double
d type : double
p type : int const*
int main()
{
// auto 的限制
// 1. 不能用于函数形参
// 2. 不能用于非静态成员变量
// 3. 无法定义数组
// 4. 无法推导模板参数
// auto 的推导规则
// 1. 当不声明为指针或引用时,auto 的推导结果是去掉初始化值得所有修饰符后的类型
// 2. 当声明为指针或引用时,auto 的推导结果是保存初始化值得所有修饰符后的类型
int x = 0;
const int z = 10;
auto e = z; // e int, auto int
e = 10;
auto f = &z; // f const int*, auto const int*
auto a = x; // a int, auto int
auto b = &x; // b int*, auto int*
auto &c = x; // c int&, auto int
const auto &d = x; // d const int&, auto int
const auto p = &x; // p const int*, auto int*
return 0;
}
#include <iostream>
#include <string>
#include <map>
// auto 常用场景
int main()
{
// 不知道该定义什么类型时可以使用 auto
auto Print = []{}; // lambda 表达式类型推导
// 标准库容器操作时可以使用 auto
std::map<std::string, int> mapTmp;
//std::map<std::string, int>::const_iterator iter = mapTmp.begin();
for (auto iter = mapTmp.begin(); iter != mapTmp.end(); ++iter) {
;
}
return 0;
}
- auto 对变量类型让编译器自动推导,对于复杂类型,极大程度方便了代码编写
- auto 并不一定是变量的实际类型,只是某个类型的占位符,编译时会替换为某个类型
- auto 声明的变量必须对此变量进行初始化,否则报错
- c++98 标准中 auto 是具有自动存储期的局部变量,实际局部变量就是自动存储的,所以 auto 用处不大,c++11 对 auto 功能重新定义
- 虽然 auto 自动类型推导很方便,尽量不要大范围使用,对复杂类型、局部推导时可以使用
decltype
int main()
{
// decltype 使用
int a = 10;
decltype (a) b = 20; // int
decltype (a + b) c = 30; // int
const int &d = a;
decltype (d) e = 20; // const int &
const int *f = &a;
decltype (f) g = &b; // const int *
const decltype (b) *h = &a; // const int *
return 0;
}
- decltype 关键字在编译时推导变量或表达式类型,语法 decltype(exp)
- 一般推导出的是带 cv 等修饰符的类型,函数返回纯右值时一般省略修饰符
- decltype 类型推导多用在泛型编程
返回类型后置
template <typename T, typename U>
auto Add(T a, U b) -> decltype (a + b) {
return a + b;
}
int main()
{
std::cout << "Add : " << Add<int, double>(10, 20.50) << std::endl;
return 0;
}
输出结果:
Add : 30.5
int Add(int a, int b) {
return a + b;
}
template <typename T, typename U>
auto AddTemplate(T a, U b) -> decltype (Add(a, b)) { // 推导函数返回值
return Add(a, b);
}
int main()
{
std::cout << "Add : " << AddTemplate<int, double>(10, 20.50) << std::endl;
return 0;
}
输出结果:
Add : 30
- c++11标准支持 auto 和 decltype 共同使用,将函数返回值类型后置语法,根据实际返回类型来推导函数返回值
模板
int main()
{
// 此处 c++11 标准允许两个尖括号连在一起(>>),c++98 标准不允许,需要空格间隔(> >)才能编译通过
std::map<int, std::map<int, int>> mapTmp;
return 0;
}
- 对模板右括号连续的改进(>>和右移运算符一样,在c++98中会报错)
// 模板的别名,定义一个 map,key 为 string 类型,value 的类型可变
//template <typename T>
//typedef std::map<std::string, T> TMap_t; // typedef 不能直接定义模板别名
template <typename T>
struct SMap_t {
typedef std::map<std::string, T> type; // c++98中定义 map的value类型可变
};
template <typename T>
using Map_t = std::map<std::string, T>; // c++11中定义 map的value类型可变
int main()
{
SMap_t<int>::type mapInt1;
SMap_t<std::string>::type mapStr1;
Map_t<int> mapInt2;
Map_t<std::string> mapStr2;
return 0;
}
- 实际上 c++11 标准的 using 关键字覆盖了 typedef 的所有功能,即使用 typedef 定义的别名都可以使用 using 实现
- using 可以对模板起别名,typedef 不能直接用于模板
template <typename T = int, typename U> // 模板默认参数
auto Add(T a, U b) -> decltype (a + b) {
return a + b;
}
int main()
{
std::cout << "Add : " << Add(10, 20.5) << std::endl;
return 0;
}
输出结果:
Add : 30.5
- c++98/03 标准中,类模板可以有默认的模板参数,但函数模板不能有默认的模板参数,c++11 标准中函数也可有默认的模板参数,而且没有写在模板参数列表最后面的限制
- 模板参数的填充顺序是从右向左
初始化列表
struct SBase {
public:
int a;
int b;
std::string str;
};
struct SBase2 {
public:
int a = 10; // 直接调用列表初始化报错
int b;
std::string str;
SBase2(int na, int nb, const std::string &s) {
a = na;
b = nb;
str = s;
}
};
int main()
{
int a {1};
int arr[] {1,2};
SBase base {1, 2, "str"};
// SBase2 base2 {1, 2, "str"}; // 此时不能直接使用列表初始化(默认构造函数)
SBase2 base2 {1, 2, "str"}; // 添加对应构造函数之后可以初始化
return 0;
}
- 初始化列表可对POD类型初始化,也可对复杂类型初始化
- 注意类初始化时有可能需要提供对应构造函数
#include <iostream>
#include <map>
#include <initializer_list> // 初始化列表头文件
struct SBase {
using map_t = std::map<int, int>::value_type;
SBase(const std::initializer_list<map_t>& init) {
for (auto iter = init.begin(); iter != init.end(); ++iter)
m_map.insert(*iter);
}
void Print() {
for (auto iter = m_map.begin(); iter != m_map.end(); ++iter)
std::cout << "key : " << iter->first << "\tvalue : " << iter->second << std::endl;
}
private:
std::map<int, int> m_map;
};
int main()
{
SBase base {{0,100}, {1, 200}, {2, 300}, {3, 400}};
base.Print();
return 0;
}
输出结果:
key : 0 value : 100
key : 1 value : 200
key : 2 value : 300
key : 3 value : 400
- c++11 提供 std::initializer_list(初始化列表) 来初始化容器,自定义容器的构造函数可以使用初始化列表来支持可变数量的初始化
- std::initializer_list 可以接受任意个数的 T 类型参数,只能被整体初始化或赋值
基于范围的 for 循环
#include <iostream>
#include <map>
#include <initializer_list>
#include <algorithm>
void PrintMap(std::map<int, int>::reference iter) { // 打印容器元素
std::cout << "key : " << iter.first << "\tvalue : " << iter.second << std::endl;
}
struct SBase {
using map_t = std::map<int, int>::value_type;
SBase(const std::initializer_list<map_t>& init) {
for (auto iter = init.begin(); iter != init.end(); ++iter)
m_map.insert(*iter);
}
void Print() { // 使用迭代器打印
for (auto iter = m_map.begin(); iter != m_map.end(); ++iter)
std::cout << "key : " << iter->first << "\tvalue : " << iter->second << std::endl;
}
void Print2() { // 使用基于范围的 for 循环打印
for (const auto & iter : m_map) // auto 推导是容器的类型,而不是迭代器类型
std::cout << "key : " << iter.first << "\tvalue : " << iter.second << std::endl;
}
void Print3() { // 使用 algorithm 提供的 for_each 循环打印
std::for_each(m_map.begin(), m_map.end(), PrintMap); // 也可以写成 lambda 函数
}
private:
std::map<int, int> m_map;
};
int main()
{
// 三种打印容器元素的方式
SBase base {{0,100}, {1, 200}, {2, 300}, {3, 400}};
base.Print();
std::cout << "----------------------\n";
base.Print2();
std::cout << "----------------------\n";
base.Print();
return 0;
}
输出结果:
key : 0 value : 100
key : 1 value : 200
key : 2 value : 300
key : 3 value : 400
----------------------
key : 0 value : 100
key : 1 value : 200
key : 2 value : 300
key : 3 value : 400
----------------------
key : 0 value : 100
key : 1 value : 200
key : 2 value : 300
key : 3 value : 400
- 基于范围的 for 循环是 for 循环的语法糖,方便代码编写
- 注意三种打印方式的异同,有兴趣可研究自定义容器支持基于范围的 for 循环