C++中级
刚学 C++的时候的基本笔记
auto
auto 也能推导一个类型,但是它会摘除掉引用和 const
所以你在 for 循环里使用 auto,如果是数字,就还好
如果是 CComPtr 就一定会复制
decltype
decltype 是 C++11 引入的关键字
用于在编译器确定表达式的静态类型(包括引用、const 等)
也就是说 decltype 这个表达式,占据的是 int 这种类型的位置
int a = 0;
const int& b = a;
decltype(b) c = a; // c 的类型是 const int&,而不是 int 或 int&
// 比如说我忘记了 b 的类型了,我这里需要推导一下
// 如果是 auto,这里 c 就变成了 int模板推到返回值的类型
template <typename T, typename U>
auto add(T x, U y) -> decltype(x + y) { // 尾置返回类型,C++11
return x + y;
}lambda 表达式推到
auto lambda = [](auto x) {
decltype(x) y = x; // y 的类型与 x 完全一致
return y;
};Using
也是 C++11 引入的
1、
using 似乎可以完全替代 typedef
using IntVector = std::vector<int>;2、
using 支持模板
template <typename T>
using MyMap = std::map<T, std::string>; // 模板别名,无法用 typedef 实现
MyMap<int> m; // 等价于 std::map<int, std::string>3、
在模板类中通过 using 定义依赖类型,简化代码
template <typename T>
class Container {
public:
using value_type = T; // 标准做法,允许外部通过 Container<T>::value_type 访问类型
// ...
};
// 用法:
Container<int>::value_type x = 10; // x 的类型是 int泛型编程
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数
template <typename T> int compare (T t1, T t2);
template <typename T> class compare;1、
template<>都是在最前面,而且是同一行(可以换行,可以不换行,但是没有;)
注意到 C++有关键词 template,而 java 使用<>则直接泛型
2、
同时,C++可以给你的类型起一个名字 T
typename 和 class 在 template 后面完全等价,即使是算两个 int 相加的函数,也可以完全用 class
class A
{
public:
int x;
int add(A* pA) {
return 0;
}
};
template <class T> int compare(T t1, T t2) {
t1.add(&t2); // 我这里调了 add,但是这是安全的,只要这个类有 add
return 0;
}
int main() {
A x{};
A y{};
compare(x, y);
return 0;
}3、
在 C++中 ,泛型方法的函数体,必须实现在头文件里,否则不能过编译,因为要生成代码
4、
类型默认参数
这个是一个没啥用的例子
class A
{
public:
int x;
int add(A* pA) {
return 0;
}
int operator+(A& other) {
return 0;
}
};
template <class T = int> int compare(T t1, T t2) {
return t1 + t2;
}
int main() {
A x{};
A y{};
compare(x, y);
compare(1, 3);
return 0;
}5、模板继承
template <class T>
class A : T
{
int x;
};
class B
{
int y;
};
int main() {
A<B> x{};
return 0;
}A 是有一些公共方法的类
B 是一些自定义类
以前我们总是让 A 去做父类,B 做子类
但是这样可以实现 A 继承 B,永远构造 A?
传统继承要求B必须派生自A,这会强制B与A形成强耦合
如果B已经继承自其他类,传统的B : A会导致多层继承,而A<B>只需作为B的派生类,避免冲突
A<T>可以改变其基类的行为。例如,若T提供某种算法策略,A<T>可复用该策略并添加额外状态(如x)
以及实现 策略模式
// 策略接口(隐式约定,非显式接口)
class B {
public:
// 策略方法:计算平方
int calculate(int value) const {
return value * value;
}
};
class C {
public:
// 策略方法:计算立方
int calculate(int value) const {
return value * value * value;
}
};
// 模板类 A 继承自策略 T
template <typename T>
class A : public T {
private:
int x;
typedef A<T> _MyBase; // 等价
using _MyBase = T;
public:
A(int x) : x(x) {}
// 复用策略的算法,并扩展功能
void execute() {
int result = this->calculate(x); // 调用策略的算法
int result = _MyBase::calculate(x); // 避免虚函数调用开销,但可能破坏多态
std::cout << "Strategy result: " << result << std::endl;
}
};返回值后置
C++11 开始
1、
lambda
2、
模板无法推导出参数
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) {
return a + b;
}返回值后置,则必定要求函数的返回值是 auto,然后 auto 用 -> 后的类型
auto func(int x, int y) -> int
{
return x + y;
}
auto func(int x, int y) //ok
{
return x + y;
}变量用 auto 必须赋值
函数的话,返回值是 auto 有时候可以不后置,推不出来就后置
std::ref
#include <iostream>
#include <functional>
#include <vector>
void increment(int& n) {
++n;
}
int main() {
int x = 10;
// 直接使用引用,不能存入容器中
// int& ref = x; // ref 只能作为别名使用
// 使用 std::ref 将 x 包装成引用包装器
std::vector<std::reference_wrapper<int>> vec;
vec.push_back(std::ref(x)); // 这样可以存入容器
// 通过引用包装器修改 x 的值
vec[0].get() = 20;
std::cout << "x = " << x << std::endl; // 输出:x = 20
// 同样可以传递给要求值传递的接口
increment(std::ref(x).get());
std::cout << "x = " << x << std::endl; // 输出:x = 21
return 0;
}std::shared_ptr
实现:
在大多数实现中,std::shared_ptr 的引用计数并不是存储在每个 shared_ptr 对象内部的成员变量,而是放在一个称为控制块(control block)的单独内存区域中,这个控制块通常是在堆上分配的
constexpr
自:C++11
可用于修饰普通变量、函数(包括模板、构造)
获得在编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被执行
1、变量
实际上是设计用来取代 const 的。
因为函数的参数 const,是表示只读,但是明显是个变量
而语句的 const,是表示这是个常量
为了区分,只读叫做 const,常量叫做 constexpr
const int a = 5 + 4;
constexpr int a = 5 + 4; //完全等价,都可以在程序的编译阶段计算出结果2、返回值是 constexpr
约束 (1)函数的函数体里,除了 return,不能有其它语句(using、assert 等无意义语句是可以的)
(2)return 的表达式,必须是常量表达式,不可以求别的值,不可以赋值
(3)像模板函数一样,使用前必须有完整的定义,而不是声明
constexpr int display(int x){
return 3 + x;
}
int main()
{
//调用常量表达式函数
int a[display(3)] = { 1,2,3,4 };
return 0;
}explicit
阻止隐式转换,只能显式通过构造函数构造
初始化参数列表
位置:放在 cpp 的实现里
类名(形参 1,形参 2,形参 3):基类类名(形参 1),成员变量(形参 2)
class MyDialog : public QDialog
{
Q_OBJECT
public:
explicit MyDialog(QDialog *parent = nullptr);
~MyDialog();
private:
int someValue; // 假设这是我们需要初始化的成员变量
};MyDialog::MyDialog(QDialog *parent)
: QDialog(parent), // 初始化基类 QDialog
someValue(42) // 初始化成员变量 someValue
{
setWindowTitle("My Non-Modal Dialog");
resize(300, 200);
}1、初始化列表的第一个参数,可以调用基类的构造函数
2、剩下的,括号外是成员变量
3、括号内一直是形参
如果你要在子类初始化之前,自定义的调用父类的初始化函数,必须这样写在初始化列表里
不能写在子类构造函数里
在初始化列表中指定基类构造函数和成员变量初始化是唯一的方法,同时能避免赋值提高效率
顺序:
调用基类构造函数。
按声明顺序初始化成员变量。
执行派生类的构造函数体。