将多个对象放置到一起视为一个整体
//定义 struct str { int x; double y; }; //声明 struct str; //仅有声明只知道str是一个struct但其内部不知道,此时str是incomplete type //但可以定义str* mystr; 所有结构体的指针大小都一样64位机8个字节
注意:
const str m_str; const结构体对象的成员是不可修改的,但可以通过定义时加mutable限定符来开后门
struct str { mutable int a = 0; int b = 3; }; int main() { str a; a.x = 1; //ok a.b = 1; //wrong }
类外定义类内声明,除了const-static对象以外其他静态成员都必须在类外初始化
struct str { static int x; //ok static int x = 10; //error const static int x = 10; //ok }; int str::x=10; //类外定义,给出一块内存,所有对象共享这块内存,不定义会出现链接错误,编译时不会出错 //即便是const static int x = 10;也需要在类外定义,此时只知道x的值,但是如果想要获取地址,还需要定义const int::x = 10; //类外定义指的是函数外定义,主函数也是函数,如果定义在函数里岂不是只有运行这个函数的时候(运行期)才会定义,所以会链接错误
const数据静态成员:
const-static数据成员可以在类内初始化,但需要在外部提供定义,否则只有值无址,而constexpr-static数据成员必须在类内初始化,因为constexpr隐含inline修饰,constexpr-static不要再在类外部定义了,冗余且可能会报错
C++17内联数据静态成员:
struct str { inline static int x = 10; //内联静态成员必须在类内部初始化 }; //不能再在类外部进行初始化定义了,否则会重定义
或者这样
struct str { static int x; }; inline int str::x = 10;
struct str { int x = 3; void fun() { std::cout<<x; //结构体内部对象无需显示说明参数 } };
struct str { int x = 3; inline void fun(); }; void str::fun() { std::cout<<x; //结构体内部对象无需显示说明参数 }
struct str { void fun() { x++; } int x; }
编译不会报错,编译器为了迎合程序员的需求(重要的写前面),采用两次编译,第一次大致看看结构体的成员都有啥,第二次再细看函数内容
类型:str* const
所有成员函数中输入有一个隐藏参数this,指向当前对象的地址&str,用于区分成员函数内部成员和形参
struct str { int x; void(int x) { x;//形参x this->x;//成员x } };
struct str { void fun() const { } //使得this变为const str* const类型 };
注: 常量类对象只能调用常量成员函数和静态成员函数,无法调用普通成员函数,故如果是只读的尽量加上const修饰
int x; struct str { int x; void fun(int x) { } }; inline void str::fun(int x) { x; //形参x ::x; //全局x this->x; //成员x str::X; //成员x }
static void fun() { } //无隐藏参数this 内部不能直接用普通的成员 //可用静态的数据成员,也可返回静态的数据成员 str::fun(); str1.fun();
利用静态成员函数构造对象
struct str { static auto& instance() { static str x; return x; } }
struct str { void fun() &; //左值调用 void fun() &&; //右值调用 void fun() const &;//常量左值调用 void fun() const &&;//常量右值调用 }; str a; a.fun(); //调用void fun() &; str().fun(); //调用void fun() &&;
struct str { private: int x; public: int y; protected: int z; };
struct str { public: void fun(str& input) { input.x; //ok } private: int x; };
打破访问限制
//声明main是类的友元函数 int main(); class str2; class str { friend str2; friend int main(); inline static int x; }; int main() { std::cout<<str::x; } class str2 { };
声明较为麻烦可以删除,但不能加限定符
struct str { friend str2; //虽然没见过,但是此处认为是一个声明 friend void fun(); //不能加限定符 friend void ::fun(); error 此时不能认为是一个声明 int x = 10; }; void fun(str& input) { std::cout<<input.x; } class str2 { };
友元函数的类内定义
class str { int y; friend void fun() { //虽然是在类内定义,但还是全局域函数 str val; std::cout<<val.x; } }; C++隐藏友元机制,虽有定义,但常规寻找找不到友元函数(可减轻编译器负担),因此无法在全局中直接调用fun(),必须在类外进行声明,或者把定义放在类外。
ADL实参依赖寻找 argument dependent lookup:非限定寻找时,除常规寻找外,还会对参数命名空间中寻找这个函数(只针对自定义类型)
class cls { friend void fun(const str val) { } }; int main() { str val; fun(val); //会在val的命名空间(cls的定义)中寻找这个函数 }
解决了隐藏友元找不到的问题,这才是隐藏友元正确的打开方式,友元函数的定义就是为了访问私有对象,不引入类对象实参,为什么要用友元函数。
class cls { public: cls(int z):x(z),y(z) //初始化列表 { } private: int x; int y; };
注意:
struct str { str() = default; str(const str& x) //必须加引用否则会无限循环,尽量再加上const :val(x.val){} int val; }; str m; //默认缺省构造函数 str m1(m); //拷贝构造函数 str m1 = m; //拷贝构造函数 显式声明默认拷贝构造函数:str(const str&) = default;
引用是对象的别名,左值引用是对左值的引用,右值引用是对右值的引用。通过右值引用使得右值的声明周期与右值引用类型变量的生命周期一样。借助右值引用实现移动语义和完美转发。完美转发见模板知识补充最后。
int x; int& y = x; //左值引用 int& z = 3; //错误,对右值左值引用 const int& z = 3; //正确 const是个例外,可以对右值左值引用 int&& a = 1; //右值引用 int&& a = x; //错误,对左值右值引用
移动语义:
std::string ori("abc"); std::string newstr = std::move(ori); //move:从输入的变量身上偷取资源,偷完之后newstr为"abc",ori为空 //move针对的是类类型对象的移动,非类类型对象可以用y = std::exchange(x,new_value)对非类型成员显示移动 //exchange在#include <utility>中
移动构造函数
struct str { str(str&& x) :a(std::move(x.a)){} std::string a = "abc"; };
注意:
struct str2 { str() = default; str2(const str2&) { std::cout<<"copy"<<std::endl; } }; struct str { str() = default; str(const str&) = default; str(str&&) = default; int val = 3; std::string a = "abc"; str2 m_str2; }; int main() { str m; str m2 = std::move(m); }
str(str&& x) { std::string tmp = x.a; std::cout<<&x<<std::endl; }
str m; str m2 = std::move(m); //调用移动构造函数 str m3 = m; //调用拷贝构造函数 m2 = m; //调用拷贝赋值函数 m2 = std::move(m) //调用移动赋值函数 str& operator = (const str& x) { val = x.val; return *this; //以实现a=b=c连等 } str& operator = (str&& x) { val = std::move(x.val); return *this; }
~str() { } //无参数返回类型,无形参。对象释放时调用
析构函数执行完进行内存回收,显式内存必须显式释放
str* m = new str(); delete m;
析构函数尽量加上noexcept修饰符
class str { public: str():ptr(new int()){} ~str(){delete ptr;} int& data() { return *ptr; } private: int* ptr; }; int main() { str a; a.data() = 3; str b(a); //错误,无拷贝构造函数,系统自动生成,其中包含a.ptr = b.ptr; //使得两个指针指向同一块内存,在析构时这块内存被释放了两次,系统崩溃。 } //改正 //增加拷贝构造函数和拷贝赋值函数 str (const str& val) :ptr(new int()) { *ptr = *(val.ptr); } str& operator = (const str& val) { *ptr = *(val.ptr); return *this; }
加上之后函数不能被调用(不是未声明)
//按常理说 str a1(20); //直接构造 str a1(a2); //直接构造 str a1 = str(2); //拷贝构造 str a1 = a2; //拷贝构造 str a1 = std::move(a2) //移动构造 //但实际上由于编译器的优化行为 str a1(a2); //a2已经存在,所以会使用拷贝构造函数 str a1 = str(2); str a1{str(2)}; //直接用直接构造来构造对象,不用先构造在移动了 //但不能给移动构造加delete,不然你就会发现这个优化的秘密 //C++17后必须进行优化,加任何编译器参数都不行
auto operator + (str x, int y = 2){} //错误 class str { auto operator () (int y = 3) { return val+y; } int val = 5; }; int main() { str x; x(); x(3); }
class str { public: str(int x):val(x){} auto operator + (str a) { return str(val + x.val); } private: int val; }; int main() { str x = 3; str y = x + 4; //x + 4可以 4 + x就不行了 }
在外定义可能会由于private属性导致无法访问一些成员,故将其声明为友元函数,在使用+运算符时由于ADL,故可以使用
struct str { str(int x):val(x){} friend auto operator + (str in1, str in2) { return str(in1.val + in2.val); } private: int val; };
friend auto& operator << (std::ostream& str, str input) { ostr<<input.val; return ostr; //用于实现类似于cout<<A<<B; }
int& operator [] (input id) { return val; //第一个参数为*this } //为保证输入对象为const时依然可用引入重载 int operator [] (int id ) const { return val; }
str& operator ++ () //前缀自增 { ++val; return *this; } str operator ++ (int x) //后缀自增 { str tmp(*this); //性能很低,会创建临时对象 ++val; return tmp; }
class str { public: str(int* p):ptr(p){} str* operator -> () { return this; } int& operator * () { return *ptr; } private: int* ptr; int val = 100; }; int main() { int x = 10; str ptr = &x; *ptr; //10 *ptr = 11; ptr -> val; //被编译器自动翻译为ptr.operator->()->val; }
class str { public: str(int x):val(x){} operator int () const //不用显式给出返回类型 { return val; } private: int val; } str obj(100); int v = obj;
以上代码可能会引入歧义 obj + 3; 可以是(int)obj + 3 也可以是 obj + (str)3,为避免歧义需要对构造函数加explicit修饰符或对重载函数加explicit,使用的时候尽量使用显式类型转换
class str { public: explicit str(int x):val(x){} operator int () const //不用显式给出返回类型 { return val; } private: int val; } str obj(100); int v = obj;
friend bool operator == (str obj1, str obj2) { return obj1.val == obj1.val; } friend bool operator == (str obj1, int x) { return obj1.val == x; } obj == 100; 100 == obj; //C++20中重载==会自动引入!= 但不能重载!=引入==
#include <compare> auto operator <=> (int x) { return val<=>x; } //返回类型strong_ordering,weak_ordering,partial_ordering //即可用于任意比较,不用在重载别的了
graph LR triangle-->shape right_triangle-->triangle rectangle-->shape square-->rectangle
header 1 | public继承 | protected继承 | private继承 |
---|---|---|---|
public int x | public int x | protected int x | private int x |
protected int y | protected int y | protected int y | private int y |
private int z | 无法继承 | 无法继承 | 无法继承 |
class Base { public: int x = 10; void fun() { std::cout<<"asd"; } }; class Derive : public Base { public: void fun2() { std::cout<<"asdasd"; fun(); } }; int main() { Derive a; Base& ref = a; ref.fun2(); //错误 ref.fun(); //正确 相当于ref只引用了Derive的Base部分 }
class Derive : public Base { int x; x; Base::x; };
struct Base { Base(int){} }; class Derive : public Base { Derive(int a) :Base(a) //若不写则系统会隐式调用缺省构造函数 { } };
非静态函数,非构造函数
class Base { public: virtual void f(){cout<<"Base::f"<<endl;} virtual void g(){cout<<"Base::g"<<endl;} virtual void h(){cout<<"Base::h"<<endl;} }; class Derived:public Base { public: virtual void f(){cout<<"Derived::f"<<endl;} virtual void g1(){cout<<"Derived::g1"<<endl;} virtual void h1(){cout<<"Derived::h1"<<endl;} };
基类对象存储结构 |
---|
vtable |
其他成员 |
其他成员 |
基类vtable中存储的是基类自己定义的虚函数的信息
Base::f() | Base::g() | Base::h() |
---|
派生类对象存储结构 |
---|
vtable |
其他基类的vtable |
其他成员 |
派生类vtable中存储的是基类中虚函数的信息(若虚函数进行了重写则覆盖基类)和自己定义的虚函数的信息
Derive::f() | Base::g() | Base::h() | Derive::g() | Derive::h() |
---|
class Base { public: virtual void func() { cout << "Base!" << endl; } }; class Derived :public Base { public: virtual void func() { cout << "Derived!" << endl; } }; void show(Base& b) { b.func(); } Base base; Derived derived; int main() { show(base); //Base! show(derived); //Derived! base.func(); //Base! derived.func(); //Derived! return 0; } 由动态类型引入的多态,同样调用一个函数, 由于输入的类型不同导致函数表现出不同的行为。
class Base { public: virtual void fun() = 0; }; class Derive : public Base { public: void fun(){} }; class Derive2 : Derive { //Derive2基类为Derive Derive中有定义故Derive2不是抽象类 };
class Base { public: Base() { fun(); } virtual void fun(){} }; class Derive : public Base { Derive() :Base() { fun(); //会先调用Base的fun在调用Derive的fun } virtual void fun(){} };
void fun() override final { ... //表明该函数不会再重写 } class Derived final: public Base { ... //表明该类不会再被继承 };
Derived* d = new Derived(); Base* b = dl delete b; //注意是用基类指针删除派生类对象时会引发不确定性,要是用派生类的指针删除派生类对象则一定会先析构派生类再析构基类,不会引发歧义。(先构造的后销毁)
class Base { public: Base(int x) :a(x){} Base(const Base& str1) :a(str1.a){} Base(Base&& str1) :a(std::move(str1.a)){} Base& operator = (const Base& val) { a = val.a; return *this; } int a; }; class Derived : public Base { public: Derived(int x) :Base(x){} Derived(const Derived& str1) :Base(str1){} Derived& operator = (const Derived& val) { Base::operator = (val); //a = val.a return *this; } };
header 1 | public继承 | protected继承 | private继承 |
---|---|---|---|
public int x | public int x | protected int x | private int x |
protected int y | protected int y | protected int y | private int y |
private int z | 无法继承 | 无法继承 | 无法继承 |
struct Base { public: int x; private: int y; protected: int z; void fun(){} }; struct Derive : public Base { public: using Base::z; //protected z -->public using Base::fun; //fun --> public 针对的是fun的所有重载版本 private: using Base::x //public x -->private };
使用using继承基类构造函数逻辑,适用于派生类并未引入新的数据成员,此时不用重写派生类的构造函数。
struct Base { public: Base(int x) :val(x){} int val; }; struct Derive : public Base { public: using Base::Base; }; Derive A(100);
struct Base { int x = 10; }; struct Derive : public Base { friend void fun(const Derive& val); }; void fun(const Derive& val) { val.x; //error错误 友元无法访问基类成员 } //且友元关系无法继承
struct Base { virtual double getvalue() = 0; virtual ~Base() = default; }; struct Derive : public Base { Derive(int x) :val(x){} double getvalue() override { return val; } int val; }; struct Derive2 : public Base { 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{13.14}); std::cout<<vec[0]->getvalue()<<vec[1]->getvalue()<<std::endl; } //将析构函数设置为虚函数为了防止delete时出错
struct Base { }; struct Base1 : public Base { }; struct Base2 : public Base { }; struct Derive : public Base1, public Base2 { };
graph LR Base1-->Base Base2-->Base Derive-->Base1 Derive-->Base2
Base里的对象Base1和Base2都有,导致Derive不知道用哪个
引入虚继承
struct Base1 : virtual Base struct Base2 : virtual Base struct Derive : public Base1, public Base2 此时编译器只会引入一个共有对象