C++
virtural继承
class AAAA { int a; };
class BBBB :public virtual AAAA { int b; char a; };
class CCCC :public virtual AAAA { int c; };
class DDDD :public BBBB, public CCCC { int d; };
内存分布:
AAAA
1>class AAAA size(4):
1> +---
1> 0 | a
1> +---
BBBB
1>class BBBB size(20):
1> +---
1> 0 | {vbptr} 虚基类指针,占8位
1> 8 | b
1> | <alignment member> (size=4)
1> +---
1> +--- (virtual base AAAA) 虚基类中的a,占4位
1>16 | a
1> +---
1>BBBB::$vbtable@: 虚基类表,偏移16的位置为虚基类AAAA
1> 0 | 0
1> 1 | 16 (BBBBd(BBBB+0)AAAA)
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> AAAA 16 0 4 0
CCCC
1>class CCCC size(20):
1> +---
1> 0 | {vbptr}
1> 8 | c
1> | <alignment member> (size=4)
1> +---
1> +--- (virtual base AAAA)
1>16 | a
1> +---
1>CCCC::$vbtable@:
1> 0 | 0
1> 1 | 16 (CCCCd(CCCC+0)AAAA)
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> AAAA 16 0 4 0
DDDD
1>class DDDD size(44):
1> +---
1> 0 | +--- (base class BBBB)
1> 0 | | {vbptr}
1> 8 | | b
1> | | <alignment member> (size=4)
1> | | <alignment member> (size=4)
1> | +---
1>16 | +--- (base class CCCC)
1>16 | | {vbptr}
1>24 | | c
1> | | <alignment member> (size=4)
1> | | <alignment member> (size=4)
1> | +---
1>32 | d
1> | <alignment member> (size=4)
1> +---
1> +--- (virtual base AAAA)
1>40 | a
1> +---
1>DDDD::$vbtable@BBBB@:
1> 0 | 0
1> 1 | 40 (DDDDd(BBBB+0)AAAA)
1>DDDD::$vbtable@CCCC@:
1> 0 | 0
1> 1 | 24 (DDDDd(CCCC+0)AAAA)
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> AAAA 40 0 4 0
virtual 虚函数
虚函数是指一个类中你希望重写(override)的成员函数,当你用一个基类指针或引用,指向一个继承类对象的时候,调用一个虚函数时, 实际调用的是继承类的版本。
class Animal {
public :
void Print()
{
cout << "Animal" << endl;
}
};
class Dog: public Animal
{
public:
void Print()
{
cout << "Dog" << endl;
}
private:
};
int main(int argc, char** argv)
{
Animal* d = new Dog();
d->Print();
}
没有使用virtual
时,函数的输出的是Animal
class Animal {
public :
virtual void Print()
{
cout << "Animal" << endl;
}
};
class Dog: public Animal
{
public:
void Print()
{
cout << "Dog" << endl;
}
private:
};
int main(int argc, char** argv)
{
Animal* d = new Dog();
d->Print();
}
内存布局:
1>class Animal size(8):
1> +---
1> 0 | {vfptr} 虚表指针
1> +---
1>Animal::$vftable@: 虚函数表
1> | &Animal_meta
1> | 0
1> 0 | &Animal::Print 可能有多个虚函数
1>Animal::Print this adjustor: 0
1>class Dog size(8):
1> +---
1> 0 | +--- (base class Animal)
1> 0 | | {vfptr}
1> | +---
1> +---
1>Dog::$vftable@:
1> | &Dog_meta
1> | 0
1> 0 | &Dog::Print
1>Dog::Print this adjustor: 0
使用virtual
后,函数的输出是Dog
没有使用virtual之前,C++对成员函数使用静态绑定,函数的调用地址在运行前就已经固定。不考虑指针指向的内容,对于什么类型的指针就调用什么类型的函数。
对于虚函数,每个派生类对象都有一个指向虚函数表的指针,访问任何虚函数都是间接通过这个指针进行的,之所以要用虚函数表,是因为调用一个虚函数的哪个版本是在运行过程(调用时指针所指的对象)才能确定的(动态绑定)。
这也可以叫运行时多态。
因此特性,若基类的析构函数是non-virtual的,容易造成局部销毁,导致资源泄漏。非常危险。
编译期多态:对模板参数而言,多态是通过模板具现化和函数重载解析实现的。以不同的模板参数具现化导致调用不同的函数,这就是所谓的编译期多态。
class Dog
{
public:
void shout(){ cout << "Dog"<<endl; }
};
class Cat
{
public:
void shout(){ cout << "Cat"<<endl; }
};
template <typename T>
void Animal(T & t)
{
t.shout();
}
int main()
{
Dog dog;
Cat cat;
animalShout(dog);
animalShout(cat);
}
在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。
virtural继承下的虚函数
class Base {
public:
int x;
virtual void func() { };
virtual void func1() { };
virtual void func2() { };
};
class Derive:public virtual Base {
public:
int y;
virtual void func1() { };
virtual void func2() { };
virtual void func3() { };
};
内存布局
1>class Base size(16):
1> +---
1> 0 | {vfptr}
1> 8 | x
1> | <alignment member> (size=4)
1> +---
1>Base::$vftable@:
1> | &Base_meta
1> | 0
1> 0 | &Base::func
1> 1 | &Base::func1
1> 2 | &Base::func2
1>Base::func this adjustor: 0
1>Base::func1 this adjustor: 0
1>Base::func2 this adjustor: 0
1>class Derive size(40):
1> +---
1> 0 | {vfptr}
1> 8 | {vbptr}
1>16 | y
1> | <alignment member> (size=4)
1> +---
1> +--- (virtual base Base)
1>24 | {vfptr}
1>32 | x
1> | <alignment member> (size=4)
1> +---
1>Derive::$vftable@Derive@:
1> | &Derive_meta
1> | 0
1> 0 | &Derive::func3
1>Derive::$vbtable@:
1> 0 | -8
1> 1 | 16 (Derived(Derive+8)Base)
1>Derive::$vftable@Base@:
1> | -24
1> 0 | &Base::func
1> 1 | &Derive::func1
1> 2 | &Derive::func2
1>Derive::func1 this adjustor: 24
1>Derive::func2 this adjustor: 24
1>Derive::func3 this adjustor: 0
1>vbi: class offset o.vbptr o.vbte fVtorDisp
1> Base 24 8 4 0
const
-
const在指针左边时,指针指向的内容是const。
-
const在指针右边时,指针是const指针
-
当const在函数名前面的时候修饰的是函数返回值。
-
在函数名后面表示是常成员函数,该函数不能修改对象内的任何成员的值,如果需要修改,要在变量前面加
mutable
关键字。 -
事实上,在某种条件下,
const
仍可能被修改,可见Effective C++ P22。
explicit
explicit关键字的作用就是防止类构造函数的隐式自动转换,只能用于修饰只有一个参数的类构造函数,与默认的implicit(隐式)相对。构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象。
class Demo { public: Demo(param) };
Demo d1(param);
Demo d2 = param; //可行,相当于Demo d1(param);
class Demo { public: explicit Demo(param) };
Demo d1(param);
Demo d2 = param; //不可行,explicit关键字禁止了这种隐式转换
static
在c++的类中,对于static成员变量是否是private数据并没有影响,因为设定static成员变量初值时,不受任何存取权限的束缚。在VS2019中对于类内的静态成员变量是由访问控制的,对于private的静态成员变量,只能在初始化时在类外操作。
static成员变量需要进行初始化操作。初始化时要带上类型名,并且不要带static。static成员变量是在初始化(而不是在类声明时候)才定义出来的。如果没有初始化操作,会产生链接错误。例如 ~ typename classname::membername; ~
lambda
语法形式如下:
[capture](parameters)->return-type {body}
capture:
- 空。没有使用任何函数对象参数。
- = 。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用Lambda所在类中的成员变量。
- a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- &a。将a按引用进行传递。
- a, &b。将a按值进行传递,b按引用进行传递。
- = ,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
- &, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
Others
-
C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
-
类内成员变量总是以其声明顺序被初始化,
-
C++对于不同编译单元(源文件)的non-local static(文件内,所有函数以外的定义的对象)对象的初始化顺序无明确定义。为了避免这个问题,可以通过函数调用的方式(返回一个local static对象),将non-local static对象改为函数内部的static对象。
-
构造函数最好使用成员初值列赋值。
Construct():value1(),value2(),value3(){}
成员初值列赋值时,赋值顺序由class中成员的生命顺序决定,不是由初始化列表中的顺序决定。初始化列表的赋值在构造函数内的代码执行前执行。
-
C++编译器会自动给空类创建构造函数、赋值函数和析构函数。(当这些函数被调用时)
class Empty{};
等价于
class Empty { public: Empty(){...} Empty(const Empty& rhs){...} ~Empty(){...} Empty& operator=(const Empty& rhs){...} }
想要阻止这一自动创建,可以将这些函数声明为
private
,并且故意不实现他们。 -
函数中通过值传递传递对象时,若一个派生类被视为基类传递,基类的构造函数会被调用,派生的特化性质被切掉。
-
函数调用,名称查找法则以及实参取决值查找规则。
-
用户可以全特化std内容template,但不允许添加任何新的其他东西,在调用时,优先选择特化版本。 见Effective C++ P107。
-
在使用依赖于模板参数的嵌套从属类型时,要在它前面放置关键字
typename
(在使用嵌套从属类型时,这个名称被假设为非类型。在前面加上typename告诉C++,这个是类型),但不能在基类列或者成员初始列前加typename。 -
C++往往拒接在继承的模板化基类内寻找继承来的名称(方法),因为模板化基类又可能被特化,而特化版本的基类可能不含有此名称。必须要使用时可以在基类函数调用前加上
this->
或者BaseClass<TemplateName>::
或在使用前添加using BaseClass<TemplateName>::FuncName;
-
函数返回局部变量时,返回的并不是这个局部变量,而是这个局部变量的一个拷贝。