(1)结构体
对基本数据结构进行扩展,将多个对象放置在一起视为一个整体
1 结构体的声明与定义(注意定义后面要跟分号来表示结束)
2 仅有声明的结构体是不完全类型( incomplete type )
3 结构体(以及类)的一处定义原则:翻译单元级别
(2)数据成员(数据域)的声明与初始化
1( C++11 )数据成员可以使用 decltype 来声明其类型,但不能使用 auto
#include
struct Str
{
decltype(3) x;
int y;
};
int main()
{
}
2 数据成员声明时可以引入 const 、引用等限定
#include
struct Str
{
const int x = 3;
int y;
};
int main()
{
}
3 数据成员会在构造类对象时定义
4( C++11 )类内成员初始化
#include
struct Str
{
int x = 3;
int y;
};
int main()
{
Str m_str; //构造对象
}
5 聚合初始化:从初始化列表到指派初始化器
#include
struct Str
{
int x;
int y;
};
int main()
{
Str m_str{3, 4};
std::cout << m_str.x << std::endl;
}
#include
struct Str
{
int x;
int y;
};
int main()
{
Str m_str{.x=3, .y=4};
std::cout << m_str.x << std::endl;
}
(3)mutable 限定符
#include
struct Str
{
mutable int x = 0;
int y = 1;
};
int main()
{
const Str m_str;
m_str.x = 3;
std::cout << m_str.x << std::endl;
}
(4)静态数据成员
多个对象之间共享的数据成员
#include
struct Str
{
int x = 0;
int y = 1;
};
int main()
{
Str m_str1;
Str m_str2;
m_str1 = 100;
std::cout << m_str2.x << std::endl;
}
1 定义方式的衍化
C++98 :类外定义, const 静态成员的类内初始化
#include
struct Str
{
static int x; //声明
int y = 1;
};
int Str::x; //定义
int main()
{
Str m_str1;
Str m_str2;
m_str1.x = 100;
std::cout << m_str2.x << std::endl;
}
C++17 :内联静态成员的初始化
2 可以使用 auto 推导类型
(5)静态数据成员的访问
1 “.” “与 ->” 操作符
2 “::” 操作符
(6)在类的内部声明相同类型的静态数据成员
#include
struct Str
{
static Str x;
};
inline Str Str::x;
int main()
{
Str m_str1;
Str m_str2;
}
可以在结构体中定义函数,作为其成员的一部分:对内操作数据成员,对外提供调用接口
在结构体中将数据与相关的成员函数组合在一起将形成类,是 C++ 在 C 基础上引入的概念
关键字 class
类可视为一种抽象数据类型,通过相应的接口(成员函数)进行交互
类本身形成域,称为类域
(1)成员函数的声明与定义
1 类内定义(隐式内联)
2 类内声明 + 类外定义
3 类与编译期的两遍处理
4 成员函数与尾随返回类型( trail returning type )
(2)成员函数与 this 指针
1 使用 this 指针引用当前对象
#include
struct Str
{
void fun()
{
std::cout << this << std::endl; //this: Str * const
}
int x;
};
int main()
{
Str m_str1;
m_str1.x = 4;
std::cout << &(m_str1) << std::endl;
m_str1.fun();
Str m_str2;
m_str2.x = 100;
std::cout << &(m_str2) << std::endl;
m_str2.fun();
}
2 基于 const 的成员函数重载
#include
struct Str
{
void fun(int x) const //this: const Str * const
{
}
void fun(int x) //this: Str * const
{
}
int x;
};
int main()
{
}
(3)成员函数的名称查找与隐藏关系
1 函数内部(包括形参名称)隐藏函数外部
2 类内部名称隐藏类外部
3 使用 this 或域操作符引入依赖型名称查找
(4)静态成员函数
在静态成员函数中返回静态数据成员
#include
struct Str
{
static i fun()
{
return x;
}
static int x;
};
int main()
{
Str m_str1;
Str::fun();
Str m_str2;
}
(1)使用 public/private/protected 限定类成员的访问权限
1 访问权限的引入使得可以对抽象数据类型进行封装
2 类与结构体缺省访问权限区别
类:缺省private
结构体:缺省public
(2)使用友元打破访问权限限制(关键字friend)
1 声明某个类或某个函数是当前类的友元(慎用)
#include
int main();
class Str
{
friend int main();
inline static int x;
};
int main()
{
std::cout << Str::x << std::endl;
}
#include
class Str2;
class Str
{
friend Str2;
inline static int x;
};
class Str2
{
void fun()
{
std::cout << Str::x << std::endl;
}
};
int main()
{
}
2 在类内首次声明友元类或友元函数
注意使用限定名称引入的友元并非友元类(友元函数)
#include
class Str
{
inline static int x;
int y;
friend void fun();
};
void fun()
{
Str val;
std::cout << val.y << std::endl;
}
int main()
{
}
#include
void fun();
class Str
{
inline static int x;
int y;
friend void ::fun();
};
void fun()
{
Str val;
std::cout << val.y << std::endl;
}
int main()
{
}
3 友元函数的类内外定义与类内定义
#include
class Str
{
inline static int x;
int y;
friend void fun() //隐藏友元
{
Str val;
std::cout << val.y << std::endl;
}
};
int main()
{
fun();
}
隐藏友元(hidden friend):常规名称无法查找到
好处:减轻编译器负担,防止误用
改变隐藏友元的缺省行为:在类外声明或定义函数
#include
class Str
{
inline static int x;
int y;
friend void fun(const Str& val)
{
std::cout << val.y << std::endl;
}
};
int main()
{
Str val;
fun(val); //除了进行常规的名称查找外,还会进行实参类型的依赖从
}
(1)构造函数:构造对象时调用的函数
1 名称与类名相同,无返回值,可包含多个重载版本
#include
class Str
{
public:
Str()
{
std::cout << "Constructor is called!" << std::endl;
}
Str(int input)
{
x = input;
std::cout << x << std::endl;
}
private:
int x;
};
int main()
{
Str m;
Str n(3);
}
2 C++11 代理构造函数
#include
class Str
{
public:
/*
Str()
{
x = 3;
}
*/
Str(int input = 3)
{
x = input;
//std::cout << x << std::endl;
}
private:
int x;
};
int main()
{
Str n(30);
}
#include
class Str
{
public:
Str() : Str(3) //代理构造函数
{
}
Str(int input)
{
x = input;
}
void fun()
{
std::cout << x << std::endl;
}
private:
int x;
};
int main()
{
Str n;
n.fun();
}
(2)初始化列表:区分数据成员的初始化与赋值
1 通常情况下可以提升系统性能
#include
#include
class Str
{
public:
Str(const std::string& input)
{
//性能不是很好:缺省初始化然后赋值
std::cout << "Pre-assignment: " << x << std::endl;
x = input; //赋值
std::cout << "Post-assignment: " << x << std::endl;
}
void fun()
{
std::cout << x << std::endl;
}
private:
std::string x;
};
int main()
{
Str n("abc");
n.fun();
}
#include
#include
class Str
{
public:
//初始化列表
Str(const std::string& input) : x(input), y(0)
{
std::cout << "Pre-assignment: " << x << std::endl;
std::cout << "Post-assignment: " << x << std::endl;
}
private:
std::string x;
int y;
};
int main()
{
Str n("abc");
n.fun();
}
2 一些情况下必须使用初始化列表(如类中包含引用成员)
#include
#include
class Str
{
public:
//初始化列表
Str(const std::string& input, int& p_i)
: x(input)
, y(0)
, ref(p_i)
{
std::cout << "Pre-assignment: " << x << std::endl;
std::cout << "Post-assignment: " << x << std::endl;
ref = 3;
}
private:
std::string x;
int y;
int& ref;
};
int main()
{
int val;
Str n("abc", val);
std::cout << val << std::endl;
}
3 注意元素的初始化顺序与其声明顺序相关,与初始化列表中的顺序无关
#include
#include
class Str
{
public:
//初始化列表
Str(const std::string& input)
: x(input)
, y(x.size())
{
std::cout << x << ' ' << y << std::endl;
}
private:
std::string x;
size_t y;
};
int main()
{
Str n("abc");
}
4 使用初始化列表覆盖类内成员初始化的行为
#include
class Str
{
public:
//初始化列表
Str()
: x(50)
, y(4)
{
std::cout << x << ' ' << y << std::endl;
}
private:
int x = 8;
int y = 1;
};
int main()
{
Str n;
}
(3)缺省构造函数:不需要提供实际参数就可以调用的构造函数
1 如果类中没有提供任何构造函数,那么在条件允许的情况下,编译器会合成一个缺省构造函数
2 合成的缺省构造函数会使用缺省初始化来初始化其数据成员
3 调用缺省构造函数时避免 most vexing parse
#include
class Str
{
public:
int x;
int y;
};
int main()
{
//Str n();
Str n;
Str p{};
}
4 使用 default 关键字定义缺省构造函数
#include
#include
class Str
{
public:
Str() = default;
Str(const std::string& input)
: x(input)
{
std::cout << x << std::endl;
}
private:
std::string x;
};
int main()
{
Str n;
/Str m(abc);
}
(4)单一构造函数
1 可以视为一种类型转换函数
#includde <iostream>
struct Str
{
Str(int x)
: val(x)
{
}
private:
int val;
};
int main()
{
Str m = 3;
}
2 可以使用 explicit 关键字避免求值过程中的隐式类型转换
#includde <iostream>
struct Str
{
Str(int x)
: val(x)
{
}
private:
int val;
};
void foo(int x)
{
}
void fun(Str m)
{
}
int main()
{
fun(3); //本应该调用foo()但是写成fun(),不会报错,但我们希望报错
}
#includde <iostream>
struct Str
{
explicit Str(int x)
: val(x)
{
}
private:
int val;
};
void foo(int x)
{
}
void fun(Str m)
{
}
int main()
{
fun(3); //报错,必须显式初始化
}
(5)拷贝构造函数
1 会在涉及到拷贝初始化的场景被调用,比如:参数传递。因此需要注意拷贝构造函数的形参类型
2 如果未显式提供拷贝构造函数,则编译器会自动合成,合成的版本会依次对每个数据成员调用拷贝构造
#includde <iostream>
struct Str
{
Str() = default;
Str(const Str& x)
: val(x.val)
{
}
private:
int val = 0;
};
int main()
{
Str m;
Str m2 = m; //Str m2(m);
}
(6)移动构造函数(C++11)
接受一个当前类右值引用对象的构造函数
1 可以从输入对象中“偷窃资源“ 只要保证传入对象处于合法状态即可
#include
#include
int main()
{
std::string ori("abc");
//std::string newStr = ori;
std::string newStr = std::move(ori); //ori里的内容转给了newStr
std::cout << newStr << std::endl;
std::cout << ori << std::endl;
}
#include
#include
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x)
: val(x.val)
, a(std::move (x.a))
{
}
void fun()
{
std::cout << val << ' ' << a << std::endl;
}
private:
int val = 3;
std::string a = "abc";
};
int main()
{
Str m;
m.fun();
Str m2 = std::move(m);
m.fun();
m2.fun();
}
2 当某些特殊成员函数(如拷贝构造)未定义时,编译器可以合成一个拷贝构造(有移动调移动,无移动调拷贝)
#include
#include
struct Str2
{
Str2() = default;
Str2(const Str2&)
{
std::cout << "Str2's copy constructor is called" << std::endl;
}
Str2(Str2&&) noexcept
{
std::cout << "Str2's move constructor is called" << std::endl;
}
};
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x) = default;
private:
int val = 3;
std::string a = "abc";
Str2 m_str2;
};
int main()
{
Str m;
Str m2 = std::move(m);
}
3 通常声明为不可抛出异常的函数
#include
#include
struct Str2
{
Str2() = default;
Str2(const Str2&)
{
std::cout << "Str2's copy constructor is called" << std::endl;
}
};
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x) noexcept = default; //移动构造函数不可抛出异常,但是拷贝构造可以
private:
int val = 3;
std::string a = "abc";
Str2 m_str2;
};
int main()
{
Str m;
Str m2 = std::move(m);
}
4 注意右值引用对象用作表达式时是左值
#include
#include
struct Str2
{
Str2() = default;
Str2(const Str2&)
{
std::cout << "Str2's copy constructor is called" << std::endl;
}
};
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x) noexcept //右值引用x
{
std::string temp = x.a; //左值
std::string temp2 = std::move(x.a) //右值
std::cout << x.a << std::endl; //左值:简单理解存在地址
}
private:
int val = 3;
std::string a = "abc";
Str2 m_str2;
};
int main()
{
Str m;
Str m2 = std::move(m);
}
(7)拷贝赋值与移动赋值函数(operator = )
#include
#include
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x) noexcept = default;
Str& operator= (const Str& x) //拷贝赋值函数
{
std::cout << "copy assigment is called" << std::endl;
return *this;
}
int val = 3;
std::string a = "abc";
};
int main()
{
Str m;
Str m2;
m2 = m; //m2.operator=(m);
}
1 赋值函数不能使用初始化列表
2 通常来说返回当前类型的引用,只有这样才支持连等
#include
#include
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x) noexcept = default;
Str& operator= (const Str& x) //拷贝赋值函数
{
val = x.val; //赋值操作
a = x.a;
return *this;
}
Str& operator= (Str&& x) //移动赋值函数
{
val = std::move(x.val); //赋值操作
a = std::move(x.a);
return *this;
}
int val = 3;
std::string a = "abc";
};
int main()
{
Str m;
Str m2;
m2 = m; //m2.operator=(m);
}
3 注意处理自身赋值的情况
#include
#include
struct Str
{
Str() = default;
Str(const Str&) = default;
Str(Str&& x) noexcept = default;
Str& operator= (const Str& x) //拷贝赋值函数
{
if (&x == this)
{
return *this;
}
val = x.val; //赋值操作
a = x.a;
return *this;
}
Str& operator= (Str&& x) // this->m; x->m
{
if (&x == this)
{
std::cout << "dummy assignment" << std::endl;
return *this;
}
std::cout << "real assignment" << std::endl;
delete ptr;
ptr = x.ptr;
x.ptr = nullptr;
val = std::move(x.val);
a = std::move(x.a);
return *this;
}
int val = 3;
std::string a = "abc";
int * ptr;
};
int main()
{
Str m;
Str m2;
m = std::move(m); //希望m的内容不会改变
m = std::move(m2);
}
4 一些情况下编译器会自动合成
#include
#include
struct Str
{
int val = 3;
std::string a = "abc";
int * ptr;
};
int main()
{
Str m;
Str m2;
m = std::move(m2);
}
(8)析构函数
1 函数名:“~” 加当前类型,无参数,无返回值
2 用于释放资源
3 注意内存回收是在调用完析构函数时才进行
4 除非显式声明,否则编译器会自动合成一个析构函数
5 析构函数通常不能抛出异常
#include
#include
struct Str
{
~Str()
{
std::cout << "Destructor is called!" << std::endl;
}
int val = 3;
std::string a = "abc";
int * ptr;
};
int main()
{
Str m;
Str m2;
Str m3;
m = std::move(m2);
m = m3;
}
(9)注意点
1 如果需要定义析构函数,那么也需要定义拷贝构造与拷贝赋值函数
#include
#include
class Str
{
public:
Str()
: ptr(new int())
{
}
~Str()
{
delete ptr;
}
int& data()
{
return *ptr;
}
private:
int * ptr; //设 &(a.ptr) = 1234, a.ptr = 5678; &(b.ptr) = 2345, b.ptr = 5678
};
int main()
{
Str a;
a.data() = 3;
std::cout << a.data() << std::endl;
Str b(a);
}
#include
#include
class Str
{
public:
Str()
: ptr(new int())
{
}
Str(const Str& val)
: ptr(new int())
{
*ptr = *(val.ptr);
}
~Str()
{
delete ptr;
}
int& data()
{
return *ptr;
}
private:
int * ptr; //设 &(a.ptr) = 1234, a.ptr = 5678; &(b.ptr) = 2345, b.ptr = 4567
};
int main()
{
Str a;
a.data() = 3;
std::cout << a.data() << std::endl;
Str b(a);
}
2 如果需要定义拷贝构造函数,那么也需要定义拷贝赋值函数
#include
#include
class Str
{
public:
Str()
: ptr(new int())
{
}
Str(const Str& val)
: ptr(new int())
{
*ptr = *(val.ptr);
}
Str& operator= (const Str& val)
{
*ptr = *(val.ptr);
return *this;
}
~Str()
{
delete ptr;
}
int& data()
{
return *ptr;
}
private:
int * ptr;
};
int main()
{
Str a;
a.data() = 3;
std::cout << a.data() << std::endl;
Str b;
b = a;
}
3 如果需要定义拷贝构造(赋值)函数,那么也要考虑定义移动构造(赋值)函数
#include
#include
class Str
{
public:
Str()
: ptr(new int())
{
}
Str(const Str& val)
: ptr(new int())
{
*ptr = *(val.ptr);
}
Str(Str&& val) noexcept
:ptr(val.ptr)
{
val.ptr = nullptr;
}
Str& operator= (const Str& val)
{
*ptr = *(val.ptr);
return *this;
}
~Str()
{
delete ptr;
}
int& data()
{
return *ptr;
}
private:
int * ptr;
};
int main()
{
Str a;
a.data() = 3;
std::cout << a.data() << std::endl;
Str b = std::move(a);
}
(10)default 关键字
只对特殊成员函数有效
(11)delete 关键字(有点迷。。。)
1 对所有函数都有效
2 注意其与未声明的区别
#include
#include
class Str
{
public:
Str()
: ptr(new int())
{
}
Str(const Str& val)
: ptr(new int())
{
*ptr = *(val.ptr);
}
Str(Str&& val) noexcept
:ptr(val.ptr)
{
val.ptr = nullptr;
}
Str& operator= (const Str& val)
{
*ptr = *(val.ptr);
return *this;
}
~Str()
{
delete ptr;
}
int& data()
{
return *ptr;
}
private:
int * ptr;
};
void fun(int) = delete;
void fun(double a)
{
std::cout << "fun(double) is called!" << std::endl;
}
int main()
{
fun(3); //会报错
/*
Str a;
a.data() = 3;
std::cout << a.data() << std::endl;
Str b = std::move(a);
*/
}
3 注意不要为移动构造(移动赋值)函数引入 delete限定符
如果只需要拷贝行为,那么引入拷贝构造即可
如果不需要拷贝行为,那么将拷贝构造声明为delete函数即可
注意delete移动构造(移动赋值)对C++17的新影响
#include
#include
class Str
{
public:
Str() = default;
Str(const Str& val) = default;
Str(Str&& val) noexcept = delete; //C++11会报错,C++17可编译运行
};
void fun(Str val)
{
}
int main()
{
fun(Str{});
}
(1)字面值类:可以构造编译器常量的类型(使用到再具体深入学习)
1 其数据成员需要是字面值类型
#include
#include
class Str
{
private:
int x = 3;
};
constexpr Str a;
int main()
{
}
2 提供 constexpr / consteval 构造函数(小心使用consteval 函数)
#include
#include
class Str
{
public:
constexpr Str(int val)
: x(val)
{
}
private:
int x;
};
constexpr Str a(3);
int main()
{
}
3 平凡的析构函数
#include
#include
class Str
{
public:
constexpr Str(int val)
: x(val)
{
}
~Str() = default;
private:
int x;
};
constexpr Str a(3);
int main()
{
}
4 提供 constexpr / consteval 成员函数(小心使用consteval 函数)
#include
#include
class Str
{
public:
constexpr Str(int val)
: x(val)
{
}
constexpr int fun() const
{
return x + 1;
}
private:
int x;
};
constexpr Str a(3);
int main()
{
return a.fun();
}
5 从C++14起 constexpr / consteval 成员函数内的数据可以通过非const的成员函数进行修改
#include
#include
class Str
{
public:
constexpr Str(int val)
: x(val)
{}
constexpr void inc()
{
x = x + 1;
}
constexpr int read() const
{
return x;
}
private:
int x;
};
constexpr int MyFun()
{
Str x(10);
x.inc();
x.inc();
x.inc();
return x.read();
}
int main()
{
return MyFun();
}
(2)成员指针
1 数据成员指针与函数成员指针
#include
#include
class Str
{
};
int main()
{
int Str::* ptr; //数据成员指针
void (Str::* ptr_fun)(); //函数成员指针
}
2 成员指针对象赋值
#include
#include
class Str
{
public:
int x;
int y;
void fun(){};
void fun(double){};
};
int main()
{
//int Str::* ptr = &Str::x;
auto ptr = &Str::x;
int Str::* ptr2 = &Str::y;
void (Str::* ptr_fun)() = &Str::fun;
//auto ptr_fun = &Str::fun; //编译器不知道调用哪个
}
注意域操作符子表达式不能加小括号(否则 A::x 一定要有意义)
#include
#include
class Str
{
public:
inline static int x;
int y;
void fun(){};
void fun(double){};
};
int main()
{
int* ptr = &(Str::x);
}
3 成员指针的使用
对象 .*成员指针
对象指针 ->* 成员指针
#include
#include
class Str
{
public:
int x;
int y;
void fun(){};
void fun(double){};
};
int main()
{
int Str::* ptr = &Str::x;
Str obj;
obj.x = 3;
std::cout << obj.*ptr << std::endl;
Str obj2;
obj2.x = 3;
Str* ptr_obj = &obj2;
ptr_obj -> x = 5;
std::cout << ptr_obj->*ptr << std::endl;
}
(3)与 bind 交互
1 使用 bind + 成员指针构造可调用对象
#include
#include
class Str
{
public:
int x;
void fun(double)
{
std::cout << x << std::endl;
};
};
int main()
{
auto ptr = &Str::fun;
Str obj;
(obj.*ptr)(100.0);
auto x = std::bind(ptr, obj, 100.0);
x();
}
2 也可基于数据成员指针构造可调用对象
#include
#include
class Str
{
public:
int x;
void fun(double)
{
std::cout << x << std::endl;
};
};
int main()
{
auto ptr = &Str::fun;
Str obj;
(obj.*ptr)(10.0);
auto ptr2 = &Str::x;
obj.*ptr2 = 3;
auto x = std::bind(ptr2, obj);
std::cout << x() << std::endl;
}
(1)使用 operator 关键字引入重载函数
#include
class Str
{
public:
int val = 2;
};
auto operator+ (Str x, Str y)
{
Str z;
z.val = x.val + y.val;
return z;
}
int main()
{
Str x;
Str y;
Str z = x + y;
std::cout << z.val << std::endl;
}
1 重载不能发明新的运算,不能改变运算的优先级与结合性,通常不改变运算含义
2 函数参数个数与运算操作数个数相同,至少一个为类类型
3 除 operator() 外其它运算符不能有缺省参数
4 可以选择实现为成员函数与非成员函数
通常来说,实现为成员函数会以 *this 作为第一个操作数(注意 == 与 <=> 的重载)
#include
class Str
{
public:
int val = 2;
auto operator() (int y = 4) const
{
return val + y;
}
};
auto operator+ (Str x, Str y)
{
Str z;
z.val = x.val + y.val;
return z;
}
int main()
{
Str x;
Str y;
Str z = x + y;
std::cout << z.val << std::endl;
std::cout << x(3) << std::endl;
}
#include
class Str
{
public:
int val = 2;
auto operator() (int y = 4) const
{
return val + y;
}
auto operator+ (Str x) const
{
Str z;
z.val = x.val + val;
return z;
}
};
int main()
{
Str x;
Str y;
Str z = x + y;
std::cout << z.val << std::endl;
std::cout << x(3) << std::endl;
}
(2)根据重载特性,将运算符进一步划分
1 可重载且必须实现为成员函数的运算符( =,[],(),-> 与转型运算符)
2 可重载且可以实现为非成员函数的运算符
3 可重载但不建议重载的运算符( &&, ||, 逗号运算符)
C++17 中规定了相应的求值顺序但没有方式实现短路逻辑
4 不可重载的运算符(如 ? :运算符)
(3)重载详述
1 对称运算符通常定义为非成员函数以支持首个操作数的类型转换
#include
class Str
{
public:
Str(int x)
: val(x)
{}
auto operator+ (Str input) const
{
std::cout << "Operator+ is called!" << std::endl;
return Str(val + input.val);
}
int val;
};
int main()
{
Str x = 3;
Str z = x + 4; //因为z = 4 + x会报错
}
#include
class Str
{
public:
Str(int x)
: val(x)
{}
int val;
};
auto operator+ (Str x, Str y)
{
std::cout << "Operator+ is called!" << std::endl;
return Str(x.val + y.val);
}
int main()
{
Str x = 3;
Str z = 4 + x;
}
#include
class Str
{
public:
Str(int x)
: val(x)
{}
friend auto operator+ (Str x, Str y)
{
std::cout << "Operator+ is called!" << std::endl;
return Str(x.val + y.val);
}
private:
int val;
};
int main()
{
Str x = 3;
Str z = 4 + x;
}
2 移位运算符一定要定义为非成员函数,因为其首个操作数类型为流类型
#include
class Str
{
public:
Str(int x)
: val(x)
{}
friend auto operator+ (Str x, Str y)
{
std::cout << "Operator+ is called!" << std::endl;
return Str(x.val + y.val);
}
friend auto& operator<< (std::ostream& ostr, Str input)
{
ostr << input.val;
return ostr;
}
private:
int val;
};
int main()
{
Str x = 3;
Str z = 4 + x;
std::cout << x << z;
}
3 赋值运算符也可以接收一般参数
#include
#include
class Str
{
public:
Str(int x)
: val(x)
{}
Str& operator= (const Str& input)
{
val = input.val;
return *this;
}
Str& operator= (const std::string& input)
{
val = static_cast<int>(input.size());
return *this;
}
int val;
};
int main()
{
Str x = 3;
x = "12345";
std::cout << x.val << std::endl;
}
4 operator [] 通常返回引用
#include
#include
class Str
{
public:
Str(int x)
: val(x)
{}
Str& operator= (const Str& input)
{
val = input.val;
return *this;
}
Str& operator= (const std::string& input)
{
val = static_cast<int>(input.size());
return *this;
}
int& operator[] (int id) //加&使得可以被赋值
{
return val;
}
int val;
};
int main()
{
Str x = 3;
x = "12345";
std::cout << x[0] << std::endl;
x[0] = 10;
std::cout << x[0] << std::endl;
}
#include
#include
class Str
{
public:
Str(int x)
: val(x)
{}
Str& operator= (const Str& input)
{
val = input.val;
return *this;
}
Str& operator= (const std::string& input)
{
val = static_cast<int>(input.size());
return *this;
}
int& operator[] (int id)
{
return val;
}
int operator[] (int id) const
{
return val;
}
int val;
};
int main()
{
Str x = 3;
x = "12345";
std::cout << x[0] << std::endl;
x[0] = 10;
std::cout << x[0] << std::endl;
const Str cx = 3;
std::cout << cx[0] << std::endl;
}
5 自增、自减运算符的前缀、后缀重载方法
#include
class Str
{
public:
Str(int x)
: val(x)
{}
Str& operator= (const Str& input)
{
val = input.val;
return *this;
}
Str& operator++ () //前缀自增
{
++val;
return *this;
}
Str operator++ (int) //后缀自增
{
Str tmp(*this);
++val;
return tmp;
}
int val;
};
int main()
{
Str s(2);
++(++s);
std::cout << s.val << std::endl;
Str s2(6);
s2++;
std::cout << s2.val << std::endl;
}
6 使用解引用运算符( * )与成员访问运算符( -> )模拟指针行为
注意 .” 运算符不能重载
“→”会递归调用 “→”操作
#include
class Str
{
public:
Str(int * p)
: ptr(p)
{}
int& operator* ()
{
return *ptr;
}
int operator* () const //只能读,不能写
{
return *ptr;
}
private:
int * ptr;
};
int main()
{
int x = 100;
Str ptr(&x);
std::cout << *ptr <<std::endl;
*ptr = 101;
std::cout << *ptr <<std::endl;
}
#include
class Str
{
public:
Str(int * p)
: ptr(p)
{}
Str* operator-> ()
{
return this;
}
int val = 5;
private:
int * ptr;
};
int main()
{
int x = 100;
Str ptr(&x);
std::cout << ptr->val <<std::endl;
std::cout << ptr.operator->()->val <<std::endl;
}
#include
class Str2
{
public:
Str2* operator-> ()
{
return this;
}
int bla = 123;
};
class Str
{
public:
Str(int * p)
: ptr(p)
{}
Str2 operator-> ()
{
return Str2{};
}
int val = 5;
private:
int * ptr;
};
int main()
{
int x = 100;
Str ptr(&x);
std::cout << ptr->bla <<std::endl;
std::cout << ptr.operator->().operator->()->bla <<std::endl;
}
7 使用函数调用运算符()构造可调用对象
#include
class Str
{
public:
explicit Str(int p)
: val(p)
{}
int operator() () const
{
return val;
}
int operator() (int x, int y, int z) const
{
return val + x + y + z;
}
private:
int val;
};
int main()
{
Str obj(100);
std::cout << obj() << std::endl;
std::cout << obj(1, 2, 3) << std::endl;
}
8 类型转换运算符
函数声明为 operator type() const
与单参数构造函数一样,都引入了一种类型转换方式
注意避免引入歧义性与意料之外的行为:通过 explicit 引入显式类型转
#include
class Str
{
public:
explicit Str(int p)
: val(p)
{}
operator int() const
{
return val;
}
friend Str operator+ (Str x, Str y)
{
return Str(x.val + y.val);
}
private:
int val;
};
int main()
{
Str obj(100);
obj + 3; //obj隐式转化为int
}
#include
class Str
{
public:
Str(int p)
: val(p)
{}
explicit operator int() const
{
return val;
}
friend Str operator+ (Str x, Str y)
{
return Str(x.val + y.val);
}
private:
int val;
};
int main()
{
Str obj(100);
obj + 3; //3隐式转化为Str
}
explicit bool 的特殊性:用于条件表达式时会进行隐式类型转换
#include
class Str
{
public:
explicit Str(int p)
: val(p)
{}
explicit operator bool() const
{
return (val == 0);
}
private:
int val;
};
int main()
{
Str obj(100);
//std::cout << obj << std::endl;
if (obj)
std::cout << 1 << std::endl;
else
std::cout << 0 << std::endl;
}
9 C++ 20 中对 == 与 <=> 的重载
通过 == 定义 !=
#include
class Str
{
public:
Str(int p)
: val(p)
{}
friend bool operator== (Str obj, Str obj2)
{
return obj.val == obj2.val;
}
private:
int val;
};
int main()
{
Str obj(100);
Str obj2(100);
std::cout << (obj == obj2) << std::endl;
std::cout << (obj != obj2) << std::endl;
}
#include
class Str
{
public:
Str(int p)
: val(p)
{}
friend bool operator== (Str obj, int x)
{
return obj.val == x;
}
private:
int val;
};
int main()
{
Str obj(100);
std::cout << (obj == 100) << std::endl;
std::cout << (100 == obj) << std::endl; //C++20支持,编译器会首先查找operator == (int, Str),如果没有,因为==可交换,进而会查找operator == (Str, int)
}
#include
class Str
{
public:
Str(int p)
: val(p)
{}
auto operator== (int x)
{
return val == x;
}
private:
int val;
};
int main() {
Str obj(100);
std::cout << (obj == 100) << std::endl;
//std::cout << (100 == obj) << std::endl;
}
通过 <=> 定义多种比较逻辑
#include
#include
class Str
{
public:
Str(int p)
: val(p)
{}
auto operator<=> (int x)
{
return val <=> x;
}
private:
int val;
};
int main() {
Str obj(100);
std::cout << (100 => obj) << std::endl;
std::cout << (obj => 100) << std::endl;
}
隐式交换操作数
注意 <=> 可返回的类型: strong_ordering, week_ordering, partial_ordering
(1)通过类的继承(派生)来引入 “是一个” 的关系
1 通常采用 public 继承( struct V.S. class )
2 继承部分不是类的声明
3 使用基类的指针或引用可以指向派生类对象
#include
class Base
{
};
class Device;
class Device : public Base
{
};
int main()
{
Device d;
Base& ref = d;
Base* ptr = &d;
}
4 静态类型 V.S. 动态类型
静态类型:编译期可以确定的类型,如Base& ref = d;
在编译期便知道 ref 的类型为 Base&
动态类型:运行期为变量实际赋予的类型,如Base& ref = d;
d的类型为派生自Base的Device类,所以ref的动态类型为Device&
5 protected 限定符:派生类可访问
(2)类的派生会形成嵌套域
1 派生类所在域位于基类内部
2 派生类中的名称定义会覆盖基类
#include
class Base
{
public:
int val = 2;
};
class Device : public Base
{
public:
void fun()
{
std::cout << val << std::endl;
}
int val = 3;
};
int main()
{
Device d;
d.fun();
}
3 使用域操作符显式访问基类成员
#include
class Base
{
public:
int val = 2;
};
class Device : public Base
{
public:
void fun()
{
std::cout << val << std::endl;
std::cout << Base::val << std::endl;
}
int val = 3;
};
int main()
{
Device d;
d.fun();
}
4 在派生类中调用基类的构造函数
#include
class Base
{
public:
Base(int)
{
std::cout << "Base Constructor is called!" << std::endl;
}
};
class Device : public Base
{
public:
Device(int a)
: Base(a)
{
std::cout << "Device Constructor is called!" << std::endl;
}
};
int main()
{
Device d(2);
}
(1)通过虚函数与引用(指针)实现动态绑定
1 使用关键字 virtual 引入
2 非静态、非构造函数可声明为虚函数
3 虚函数会引入vtable结构
#include
class Base
{
public:
virtual void baseMethod(){}
int baseMember;
};
class myClassDerived : public Base
{
public:
virtual void derivedMethod(){}
int derivedMember;
};
class myClassDerived2 : public myClassDerived
{
public:
virtual void derivedMethod2(){}
int derivedMember2;
};
int main()
{
myClassDerived2 d;
Base& b = d;
Base* ptr = &d;
myClassDerived2& d2 = dynamic_cast<myClassDerived2&>(b);
myClassDerived2* ptr2 = dynamic_cast<myClassDerived2*>(ptr);
}
(2)虚函数在基类中的定义
1 引入缺省逻辑
2 可以通过 = 0 声明纯虚函数,相应地构造抽象基类
#include
class Base
{
public:
virtual void fun() = 0;
};
class Derive : public Base
{
public:
void fun()
{
std::cout << "Derive::fun() is called!" << std::endl;
}
};
(3)虚函数在派生类中的重写( override )
1 函数签名保持不变(返回类型可以是原始返回指针 / 引用类型的派生指针 / 引用类型)
2 虚函数特性保持不变
3 override 关键字(C++11)
#include
class Base
{
public:
virtual void fun()
{
std::cout << "Base::fun() is called!" << std::endl;
}
};
class Derive : public Base
{
public:
void fun() override
{
std::cout << "Derive::fun() is called!" << std::endl;
}
};
void proc(Base& b)
{
b.fun();
}
int main()
{
Derive d;
d.fun();
Base& b = d;
b.fun();
Base c;
proc(d);
proc(c);
}
(4)由虚函数所引入的动态绑定属于运行期行为,与编译期行为有所区别
1 虚函数的缺省实参只会考虑静态类型
#include
class Base
{
public:
virtual void fun(int x = 3)
{
std::cout << "Base: " << x << std::endl;
}
};
class Derive : public Base
{
public:
void fun(int x = 4) override
{
std::cout << "Derive: " << x << std::endl;
}
};
void proc(Base& b)
{
b.fun();
}
int main()
{
Derive d;
proc(d);
}
编译期:
#include
class Base
{
public:
inline virtual void fun(int x)
{
std::operator<<(std::cout, "Base: ").operator<<(x).operator<<(std::endl);
}
// inline constexpr Base() noexcept = default;
};
class Derive : public Base
{
public:
inline virtual void fun(int x)
{
std::operator<<(std::cout, "Derive: ").operator<<(x).operator<<(std::endl);
}
// inline constexpr Derive() noexcept = default;
};
void proc(Base & b)
{
b.fun(3);
}
int main()
{
Derive d = Derive();
proc(static_cast<Base&>(d));
return 0;
}
2 虚函数的调用成本高于非虚函数
final 关键字
class Derive : public Base
{
public:
void fun(int x = 4) override final
{
std::cout << "Derive: " << x << std::endl;
}
};
class Derive final : public Base
{
public:
void fun(int x = 4) override
{
std::cout << "Derive: " << x << std::endl;
}
};
3 为什么要使用指针(或引用)引入动态绑定
void proc(Base& b)
{
b.fun();
}
int main()
{
Derive d;
proc(d);
}
没有引用,那么编译器会使用 d 构造 Base,所以会调用 Base 中的 fun()
使用指针(引用),会指向原有的对象
4 在构造函数中调用虚函数要小心
#include
class Base
{
public:
Base()
{
fun();
}
virtual void fun()
{
std::cout << "Base: " << std::endl;
}
};
class Derive : public Base
{
public:
Derive()
: Base()
{
fun();
}
void fun() override
{
std::cout << "Derive: " << std::endl;
}
};
void proc(Base b)
{
b.fun();
}
int main()
{
Derive d;
}
5 派生类的析构函数会隐式调用基类的析构函数
#include
class Base
{
public:
~Base()
{
std::cout << "~Base: " << std::endl;
}
};
class Derive : public Base
{
public:
~Derive()
{
std::cout << "~Derive: " << std::endl;
}
};
int main()
{
Derive * d = new Derive();
delete d;
}
6 通常来说要将基类的析构函数声明为 virtual (基类的指针挂载派生类时)
使用派生类的指针时可以不使用 virtual
#include
#include
class Base
{
public:
virtual ~Base()
{
std::cout << "~Base: " << std::endl;
}
};
class Derive : public Base
{
public:
~Derive()
{
std::cout << "~Derive: " << std::endl;
}
};
int main()
{
std::unique_ptr<Base> ptr(new Derive());
}
7 在派生类中修改虚函数的访问权限
#include
class Base
{
protected:
virtual void fun()
{
std::cout << "Base: " << std::endl;
}
};
class Derive : public Base
{
public:
void fun() override
{
std::cout << "Derive: " << std::endl;
}
};
int main()
{
Derive d;
d.fun();
Base& b = d;
//b.fun();
}
(1)派生类合成的:
1 缺省构造函数会隐式调用基类的缺省构造函数
2 拷贝构造函数将隐式调用基类的拷贝构造函数
3 赋值函数将隐式调用基类的赋值函数
#include
class Base
{
public:
Base()
{
std::cout << "Base default constructor is called!" << std::endl;
}
Base(const Base& val)
{
std::cout << "Base copy constructor is called!" << std::endl;
}
Base& operator= (const Base&)
{
std::cout << "Base assignment is called!" << std::endl;
return *this;
}
};
class Derive : public Base
{
public:
};
int main()
{
Derive d; //1
Derive x(d); //2
x = d; //3
}
(2)派生类的析构函数会调用基类的析构函数
#include
class Base
{
public:
Base()
{
std::cout << "Base default constructor is called!" << std::endl;
}
Base(const Base& val)
{
std::cout << "Base copy constructor is called!" << std::endl;
}
Base& operator= (const Base&)
{
std::cout << "Base assignment is called!" << std::endl;
return *this;
}
~Base()
{
std::cout << "Base destructor is called!" << std::endl;
}
};
class Derive : public Base
{
public:
~Derive()
{
std::cout << "Derive destructor is called!" << std::endl;
}
};
int main()
{
Derive d;
Derive x(d);
x = d;
}
(3)派生类的其它构造函数将隐式调用基类的缺省构造函数
#include
class Base
{
public:
Base()
{
std::cout << "Base default constructor is called!" << std::endl;
}
Base(int)
{
std::cout << "Base int constructor is called!" << std::endl;
}
Base(const Base& val)
{
std::cout << "Base copy constructor is called!" << std::endl;
}
};
class Derive : public Base
{
public:
Derive() = default;
Derive(int)
{
}
Derive(const Derive&)
{
}
};
int main()
{
Derive d(3);
Derive d2;
Derive x(d2);
}
(4)所有的特殊成员函数在显式定义时都可能需要显式调用基类相关成员
#include
class Base
{
public:
Base()
{
std::cout << "Base default constructor is called!" << std::endl;
}
Base(int)
{
std::cout << "Base int constructor is called!" << std::endl;
}
Base(const Base& val)
{
std::cout << "Base copy constructor is called!" << std::endl;
}
};
class Derive : public Base
{
public:
Derive()
: Base(0)
{}
Derive(const Derive& input)
: Base(input)
{
}
};
int main()
{
Derive d2;
Derive x(d2);
}
(5)构造与销毁顺序
基类的构造函数会先调用,之后才涉及到派生类中数据成员的构造
派生类中的数据成员会被先销毁,之后才涉及到基类的析构函数调用
(1)public 与 private 继承
1 public 继承:描述 “是一个” 的关系
2 private 继承:描述 “根据基类实现出” 的关系
3 protected 继承:几乎不会使用
(2)using 与继承
1 使用 using 改变基类成员的访问权限
先决条件:派生类可以访问该成员
无法改变构造函数的访问权限
#include
class Base
{
public:
int x;
void fun(int){}
private:
int y;
protected:
int z;
void fun()
{}
};
class Derive : public Base
{
public:
using Base::z;
using Base::fun;
private:
using Base::x;
};
int main()
{
Derive d;
d.z;
d.fun();
}
2 使用 using 继承基类的构造函数逻辑
#include
class Base
{
public:
Base(int val){}
};
class Derive : public Base
{
public:
using Base::Base;
};
int main()
{
Derive d(100);
}
3 using 与部分重写(using 优先级低于重写)
#include
class Base
{
protected:
virtual void fun()
{
std::cout << "1\n";
}
virtual void fun(int)
{
std::cout << "2\n";
}
};
class Derive : public Base
{
public:
using Base::fun;
void fun(int) override
{
std::cout << "3\n";
}
};
int main()
{
Derive d;
d.fun();
d.fun(2);
}
#include
class Base
{
protected:
Base() {}
};
class Derive : public Base
{
public:
using Base::Base;
//编译器会合成缺省构造函数,合成的缺省构造函数会调基类的构造函数,即using没有改变构造函数的访问权限
//Derive() = default;
};
int main()
{
Derive d;
}
(3)继承与友元
友元关系无法继承,但基类的友元可以访问派生类中基类的相关成员
派生类友元无法访问基类成员
#include
class Derive;
class Base
{
public:
friend void fun(const Derive&);
private:
int x = 10;
};
class Derive : public Base
{
private:
int y = 5;
};
void fun(const Derive& val)
{
std::cout << val.x << std::endl;
}
int main()
{
Derive d;
fun(d);
}
(4)通过基类指针实现在容器中保存不同类型对象
#include
#include
#include
class Base
{
public:
virtual double getValue() = 0;
virtual ~Base() = default;
};
class Derive : public Base
{
public:
Derive(int x)
: val(x){}
double getValue() override
{
return val;
}
int val;
};
class Derive2 : public Base
{
public:
Derive2(double x)
: val(x){}
double getValue() override
{
return val;
}
double val;
};
int main()
{
std::vector<std::shared_ptr<Base>> vec;
vec.emplace_back(new Derive{1});
vec.emplace_back(new Derive2{3.14});
std::cout << vec[0]->getValue() << std::endl;
std::cout << vec[1]->getValue() << std::endl;
}
(5)多重继承与虚继承
多重继承使用情况较少
#include
class Base1
{
public:
virtual ~Base1() = default;
};
class Base2
{
public:
virtual ~Base2() = default;
};
class Derive : public Base1, public Base2
{
public:
};
int main()
{
}
虚继承
#include
class Base
{
public:
virtual ~Base() = default;
int x;
};
class Base1 : public virtual Base
{
public:
virtual ~Base1() = default;
};
class Base2 :public virtual Base
{
public:
virtual ~Base2() = default;
};
class Derive : public Base1, public Base2
{
public:
};
int main()
{
Derive d;
d.x;
}
(6)空基类优化与[[no_unique_adddress]]属性C++20
#include
class Base
{
public:
};
class Derive
{
public:
int x;
Base b;
};
int main()
{
std::cout << sizeof(Base) << std::endl;
std::cout << sizeof(Derive) << std::endl;
}
#include
class Base
{
public:
};
class Derive : public Base
{
public:
int x;
};
int main()
{
std::cout << sizeof(Base) << std::endl;
std::cout << sizeof(Derive) << std::endl;
}
#include
class Base
{
public:
};
class Derive
{
public:
int x;
[[no_unique_address]] Base b;
};
int main()
{
std::cout << sizeof(Base) << std::endl;
std::cout << sizeof(Derive) << std::endl;
}