1、常量有前缀和后缀
前缀:0x,0
后缀:U,L
2、thread_local 存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问
3、引用:
引用变量一个别名,也就是说,它是某个已存在变量的另一个名字。
不存在空引用。引用必须连接到一块合法的内存。
一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
引用必须在创建时被初始化。指针可以在任何时间被初始化。
①:
void swap(int& x, int& y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
②
double& setValues( int i )
{
return vals[i]; // 返回第 i 个元素的引用
}
即可以对形参进行修改影响到实参,可以对返回值进行修改影响到返回值变量
4,
流插入运算符 <<
流提取运算符 >>
5、
访问控制:public protected private
默认控制权限为private
继承方式:
class A : public B
{
public:
void AA(){}
}
6、多继承
class A : public B,protected C
{
}
7、虚拟继承,虚基类,虚函数,抽象类,接口
没有声明虚函数的函数被继承时覆盖,属于前期绑定(编译器绑定),始终调用声明对象的函数,而虚函数则会调用定义对象的函数。要使
用多态需要使用指针做配合。
8、构造函数初始化列表
常量和引用必须使用初始化列表进行初始化
class A{
const int a;
int &b;
A(int x,int y):a(x),b(y)
{
}
}
初始化顺序:
构造函数调用顺序为:
- 虚基类(按照继承顺序调用)
- 基类(按照继承顺序调用)
- 成员对象(按照成员对象声明顺序调用)
- 自己
自己的初始化顺序为:
- 成员声明时初始化
- 成员初始化列表初始化
- 构造函数成员赋值初始化
整体看变量的初始化顺序就应该是:
- 基类的静态变量或全局变量
- 派生类的静态变量或全局变量
- 基类的成员变量
- 派生类的成员变量
#include <iostream>
using namespace std;
class A
{
public:
int a = 1; //first
A() {}
A(int a_):a(a_){} //second
A(int a_, bool b) :a(4) { a = a_; } //third
};
int main()
{
A a1, a2(3), a3(5, true);
cout << "a1.a=" << a1.a << endl;//1
cout << "a2.a=" << a2.a << endl;//3
cout << "a3.a=" << a3.a << endl;//5
system("pause");
return 0;
}
①c++11前成员变量可能不能在声明时初始化,但是c++11后可以了。
#include<iostream>
using namespace std;
class CC{
public:
void out(){
cout<<"ccc"<<endl;
}
};
class BB:public AA{
public:
CC *cc=new CC();//正确的初始化
};
int main(){
BB b;
b.cc->out();//ccc
}
②静态成员变量如果要在类内初始化,需要声明为常量,并且赋值为常数。
9、运算符重载
Box operator+(const Box&);
10、new和delete
int a=new int;
deltete a;
int* a=new int[10];
delete [] a;
int** a=new int*[10];
delete [] a;
11、模板
函数模板:
template <typename T> inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
类模板:
template <class T> class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
12、复制构造函数、浅拷贝和深拷贝
两种方式:
复制初始化(int a = 5;)(隐式初始化)
直接初始化(int a(5);) (显式初始化)
explicit声明构造函数时表示只能用显示构造器,例如:
class Test1{
public:
explicit Test1(int i){}
};
此时不能用Test1 test=1;
可以用Test1 test(1);
浅拷贝:
浅拷贝是针对显示初始化,当类中包含指针时,如果没有定义拷贝构造函数,会调用默认拷贝构造函数,而默认拷贝构造函数仅仅会将类中的成员对象进行直接复制,也就是指针的话仅仅将指针指向同一个对象,这时就叫做浅拷贝。
例如
#include<stdlib.h>
#include<iostream>
class Student{
public:
char *name;
Student(){
std::cout<<"struc"<<std::endl;
name=new char(20);
}
~Student(){
std::cout<<"destruc"<<std::endl;
free(name);
}
};
int main(void){
Student a;
Student b(a);
return 0;
}
执行后结果为:
.\testclass
struc
destruc
destruc
13、线程使用:
创建线程时会自动执行 thread t1(task1,arg1);
创建好后要选择执行方式t1.detach()或者t1.join();
14、内联函数:
当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插人调用语句处,就像整个函数
体在调用处被重写了一遍一样。
inline int Max (int a, int b)
{
if(a >b)
return a;
return b;
}
15、数组分配和c的区别
c:
c++:
string a="123";//不需要delete;
string *b=new string("456");//delete b;
string *c=new string[5];//dekete [] c;
string d("123");
16、仿函数function
①
模板类实现函数指针的功能
int add(int a,int b)
{
return a+b;
}
std::function<int(int,int)>func=add;
②
使用bind改变参数
function<void()>func=bind(add,1,2);
17、对象声明与初始化
class A{ A(){} A(int a){};};
int main(){
A a1;//在栈中分配了一块内存保存A类的对象a1,a1可以直接使用
A *a2=new A();//在栈中分配了一块类指针大小a2保存指向堆内存的一个指针,堆中分配类大小的区域保存类对象。
delete a2;
A a3(1);//栈分配
A a4=1;//站分配
}
18、强制类型转换
①
double a = 1.999;
int b = static_cast<int>(a);
②
ANIMAL * ani3 = new DOG;
DOG* dog3 = static_cast<DOG*>(ani3);
dog3->OutPutname();
19、线程存储
① thread_local关键字,被此关键字声明的变量具有线程周期,线程创始时创建,结束时销毁。
②
static pthread_key_t key;
void func1()
{
pthread_setspecific(key,"func1");//可以线程的任何位置获取次参数或者变量
}
19、结构体和类
c++中结构体和类以及没有太大差别,结构体可以继承结构体和类,类也可以继承结构体。
区别:
默认权限:
①结构体默认声明权限为public,默认继承权限为public,类则都为private。
初始化方法:
②结构体在不定义构造函数时能用={}来初始化,定义private的构造函数时不能用{}初始化,而class在定义public的构造函数后也能用{}来初始化。
一般来说,结构体用来当做数据结构,类用来实现对象。
20、运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 自增运算符 | ++变量名 | 单目运算符 | ||
-- | 自减运算符 | --变量名 | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式 / 表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式%整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式<=表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
在*a->b过程中,先算->,再算*
在(c)a.b过程中,先算a.b,再算(c)。也就是先算a应用b,再把b的结果强制转换为c
int c = 0;
int *d = &c;
++*d;//右结合,先运算*d,再运算++
printf("c:[%d]\n", c);//输出1
int e = 0;
int *f = &e;
*d++;//右结合,先运算++,再运算*d
printf("e:[%d]\n", e);//输出0
21、有名命名空间和无 名命名空间
有名命名空间使用形式为 name:name:varible或者name:name:type。
例如namespace A{ class B{}} .使用为A::B b;注意当实现B的成员函数(这里以构造函数为例)时,A::B::B(){} ,这里命名空间和类作用域的两个双冒号在一起,虽然都起到作用域限定作用,但是前者作用域命名空间,后者作用于类。同时还有一种作用于类的静态函数表示,例如class C{ public: static void func1(){}},使用方法为C::func1()。所以当看到A::B::C::Func();时,C可能是命名空间,这时Func表示命名空间内的函数。C也可能是类,这时Function表示C内的静态成员。这里再提醒一点双冒号的第三种作用,表示全局变量,例如 int a; void fun1(){int a; ::a=1;/*表示全局变量a*/}
无名命名空间,使用形式为namespace{},就是没有名字的命名空间,当使用无名命名空间时就表示空间内的内容只能在空间声明处到本文件结束为止生效,在无名命名空间前和其他文件都不生效。
例如namespace {int a=1} void main(){a=2} //其他文件无法使用此变量
22 .Const在函数前与在函数后
我们一般见到const是在函数前,表示返回值为常量,例如const int getValue();但是如果有这样的函数声明,int getValue() const ;是表示什么意思呢,我们在C语言里一般看不到这种用法,因为它用于表明此函数不能修改成员对象。
例如:
#include<iostream>
class Test11{
public:
int value;
int getValue() const{
return value;
}
void setValue(int val) const{
value=val;
}
};
int main(void)
{
return 0;
}
编译结果为:
d:\VScode\threadtest>g++ const_test.cc -o const_test
const_test.cc: In member function 'void Test11::setValue(int) const':
const_test.cc:10:19: error: assignment of member 'Test11::value' in read-only object
value=val;
^~~
d:\VScode\threadtest>
23、函数默认参数
默认参数是c++的特性,不能再c语言中使用
例如 void func(int a=0);//c中编译失败
24、不实现类成员函数也能编译链接通过的情况
此情况只针对非虚函数,当有虚函数并且虚函数未实现时会链接错误,但是纯虚函数并且纯虚函数未实现时会编译失败
25、虚函数和虚继承
a:多态的关键是虚函数,而不是虚继承
例如:
#include<stdlib.h>
#include<iostream>
class A{
public:
virtual void name(){
std::cout<<"A"<<std::endl;
}
};
class B:public A{
public:
virtual void name(){
std::cout<<"B"<<std::endl;
}
};
class C:public virtual A{
public:
virtual void name(){
std::cout<<"C"<<std::endl;
}
};
int main(void){
//B为继承,C为虚继承,都能实现多态
A* a=new A();
a->name();
A* b=new B();
b->name();
A* c=new C();
c->name();
return 0;
}
结果为:
.\testclass
A
B
C
多态产生的原因是当一个类里面包含虚函数或者纯虚函数时,编译器会在此类中生成一个vftable虚拟函数表。虚拟函数表包含此类声明的虚函数地址。然后此类引用这个函数时是通过虚拟函数表中的地址来引用,这样当继承这个虚类时,虚拟函数表也会被继承,当子类覆写虚函数时,虚拟函数表vftable中的地址也会更新。这样就算运行时子类被转换为父类,但是引用方式仍然是通过虚拟函数表来引用,所以能够引用到子类的函数。
b:虚继承的作用是避免多继承造成子类包含多个相同的父类
#include<stdlib.h>
#include<iostream>
class A{
public:
int a;
};
class B:virtual public A{
public:
int b;
};
class C:virtual public A{
public:
int c;
};
class D:public C,public B{
};
class E:virtual public C,virtual public B{
};
int main(void){
A a;
B b;
C c;
D d;
E e;
std::cout<<"size A:"<<sizeof(a)<<std::endl;
std::cout<<"size B:"<<sizeof(b)<<std::endl;
std::cout<<"size C:"<<sizeof(c)<<std::endl;
std::cout<<"size D:"<<sizeof(d)<<std::endl;
std::cout<<"size E:"<<sizeof(e)<<std::endl;
d.a;
e.a;
return 0;
}
.\testclass
size A:4
size B:16
size C:16
size D:32
size E:40
从上面的结果中看出,如果仅仅是继承的话,D包含A和B的成员对象,大小就是16+16=32。而当虚继承时,还会额外多加8个字节,这里面就有4个字节保存着虚拟基类表vbtable,虚基类表的作用是所有A的虚子类中的虚基类表都包含同样的A地址,这样B和C之中的A就是唯一的了。之所以D和E都能区分成员对象a的原因是类B和类C引用的是同一个a,那为什么B的大小为16,C的大小为16,D的大小为32呢,如果引用同一个a不应该是32-4=28吗?这个问题产生的原因是引用a只发生在运行阶段,而sizeof发生在编译期,而编译器认为D的大小就是B和C相加,所以就是32字节。
26,C++四种强制类型转换的区别
(1)
a:static_cast:
常用于基本变量转换,类上行转换(等价于旧式强制转换)。通常等价于显示转换(int)1.1
例如:
int a=1;
double b=static_cast<double>(a);
int c=static_cast<int>(b);
b:const_cast:
常用与常变量指针转变量指针,仅仅针对指针或者引用。
例如:
int a=1;
const int *b=&a;
int *c=const_cast<int*>(b);
c:reinterpret_cast:
常用与整形与指针互转,函数指针转化。
例如:
typedef void(vfunci(int));
typedef void(vfuncv());
int main(void){
int a=1;
int *b=&a;
long long c=(long long)(b);
long long d=reinterpret_cast<long long>(b);//和上面的等价
std::cout<<"a:"<<a<<std::endl;
std::cout<<"*b:"<<*b<<std::endl;
std::cout<<"c:"<<c<<std::endl;
vfunci *e = test_func1;
e(1);
vfunci *f = reinterpret_cast<vfunci*>(test_func2);
f(2);
}
d:dynamic_cast
常用于类下行转换,仅仅针对包含虚函数的类,模板必须为引用或者类指针。此强制转换发生在运行时,而前面三个发生在编译期
例如:
class A{
public:
void virtual ttest(){
std::cout<<"A"<<std::endl;
}
};
class B:virtual public A{
public:
void virtual ttest(){
std::cout<<"B"<<std::endl;
}
};
int main(void){
A a;
A *b=new B();
B* c=dynamic_cast<B*>(b);
c->ttest();
}
(2)dynamic_cast与static_cast对比
a:static_cast能转换基本类型,对象类型(上行转换),对象指针类型,但是dynamic_cast只能转换对象指针
b:对于转换对象指针来说,static_cast对于对象指针没有限制,但是dynamic_cast在做下行转换时必须要对象含有虚函数
c:对于转换对象指针来说,static_cast能做上行转换,下行转换,转换过程中使用的是静态检查,也就是判断是否转换的两个对象有继承关系,如果没有,则在编译期就报错。如果有继承关系,则编译期OK。如果做下行转换,并且运行期调用了子类特有的函数,则运行期可能崩溃(参考下面第24点)。dynamic_cast做上行转换时,和static_cast没有区别;但是如果做下行转换则需要对象有虚函数,因为对象的运行时类型信息保存在虚函数表中。但是运行期也会进行类型检查,如果原对象实际上不是要转换的对象,则不会报错,但是转换后的值为NULL。如果转换目标
指针类型转换总结:
static_cast:类上行转换和类下行转换,没有继承关系则编译期不通过。编译通过后和reinterpret_cast一样。
dynamic_cast:编译期发现是下行转换时需要检查对象是否包含虚函数,如果不是下行转换即使没有虚函数表或者没有继承关系编译期间也可以通过。还有一点和其他两个不一样的地方,下行转换中如果发现转换的类是父类,泽返回NULL。
class AA{};
class BB:public AA{};
class CC{};
int main(){
BB* b1=dynamic_cast<BB*>(new AA());//返回NULL ,因为要转换的类为父类
AA* a1=new BB();
BB* b2=dynamic_cast<BB*>(a1);//返回正常 ,因为要转换的类为子类
}
reinterpret_cast:编译期间什么都不检查,无论是否包含虚函数或者是否有无继承关系都可以通过。
另外无能怎么转换指针类型,只要声明类型不包含这个成员函数,则编译期通不过。
27、&符号作引用和取地址的区别。
当&在类型和变量之间时,表示引用,当&仅在变量前时,表示取地址,例如:
func1(int &a){
a=2;//表示引用,改变a将改变实参
}
func1(int a){
auto b=&a;//b将获取形参a的地址
}
28、c++对象赋值
当直接把一个对象的值付给另一个对象时,调用是时对象的赋值函数,也就是=符号重载。
#include<string>
#include<iostream>
using namespace std;
class Foo{
public:
int a;
Foo(){
cout<<"Foo construction"<<endl;
}
Foo(int aa){
a=aa;
cout<<"Foo construction a"<<endl;
}
Foo(const Foo & foo){
cout<<"Foo construction copy"<<endl;
}
~Foo(){
cout<<"Foo distruction a:"<<a<<endl;
}
};
int main(void){
Foo a=Foo(1);//只对Foo(1)进行了一次初始化,然后对a进行了一次赋值函数进行赋值
cout<<"-------"<<endl;
Foo b;
b=Foo(1);//对Foo b进行一次初始化,然后又对Foo(1)进行了一次初始化,最后调用b的赋值函数
cout<<"-------"<<endl;
Foo c=1;//调用了隐式构造函数
cout<<"-------"<<endl;
Foo d(1);//调用了显示构造函数
cout<<"-------"<<endl;
Foo e(a);//调用了拷贝构造函数
cout<<"----end---"<<endl;
}
/*
Foo construction a
-------
Foo construction
Foo construction a
Foo distruction a:1
-------
Foo construction a
-------
Foo construction a
-------
Foo construction copy
----end---
Foo distruction a:4199705
Foo distruction a:1
Foo distruction a:1
Foo distruction a:1
Foo distruction a:1
*/
29、C++容器遍历
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
void printfstring(string str){
cout<<str<<endl;
}
int main(void)
{
vector<string> ary={"1","2"};
//迭代器遍历
vector<string>::iterator ite;
for(ite=ary.begin();ite!=ary.end();ite++){
cout<<*ite<<endl;
}
//下标遍历
cout<<endl;
for(auto i:vector<int>({0,1})){
cout<<ary[i]<<endl;
}
//for_each算法遍历
cout<<endl;
string tmp;
for_each ( ary.begin(),ary.end(),printfstring ) ;
//auto关键字遍历
cout<<endl;
for(auto tmp:ary){
cout<<tmp<<endl;
}
}
30、c++类成员初始化
A、对于普通成员而言,不需要注意什么,既可以在构造器中初始化,也可以在要使用的时候初始化,也可以直接在声明的地方进行初始化。
B、对于static成员而言,不能在声明的地方进行初始化,也不能在类构造函数里面进行初始化,只能在类外全局部分进行初始化。如果不在类外进行初始化会在使用此变量时提示找不到这个变量。
C、同样对于static const成员,既能在类外全局部分进行初始化,也可以在声明的地方初始化。
D、对于const成员,既可以在声明的地方进行初始化也可以在构造器中初始化,但是不能不进行初始化,这个就是和普通成员不一样的地方。
#include<iostream>
using namespace std;
class Foo{
public:
static int a;
const int b;
static const int c;//const static 可以在类外赋值
int d=4;
static const int e=5;//const static 还可以在声明时赋值
Foo():b(2)
{
}
};
int Foo::a=1;
const int Foo::c=3;
int main(void)
{
Foo aa;
cout<<aa.a<<","<<aa.b<<","<<aa.c<<","<<aa.d;//1,2,3,4
return 0;
}
31、final关键字
A、修饰class,表示class不能被继承
B、修饰成员函数,表示成员函数不能被覆写。
32、定义一个空类编译期做了什么
也就是编译期默认生成的内容:
1、默认构造函数
2、默认拷贝构造函数
3、默认赋值运算函数
4、默认析构函数
例如
class Foo{
}
编译后则是
class Foo{
public:
Foo(){}//构造
Foo(Foo foo){};//拷贝构造
Foo & operator=(const Foo){}//赋值运算
~Foo(Foo foo){};//析构
}
33、栈上对象不一定在出函数时进行析构,如果一个栈上对象在大括号包括起来的代码块里,则在出大括号时就会析构。
#include<iostream>
using namespace std;
class Foo{
public:
int _a;
Foo(int a):_a(a){
_a=a;
cout<<"Foo construct:"<<_a<<endl;
}
~Foo(){
cout<<"Foo destruct:"<<_a<<endl;
}
}
int main(){
Foo foo1(1);
cout<<"-------"<<endl;
{
Foo foo2(2);//出代码块时会析构
}
cout<<"-------"<<endl
}
/* 输出:
Foo construct:1
-------
Foo construct:2
Foo destruct:2
-------
Foo destruct:1
*/
34、父类指针强制转化为子类时,子类指针可以调用父类函数和子类函数,但是调用子类函数有风险,可能会崩溃。
#include<iostream>
using namespace std;
class AA{
public:
char aa[5]={'a','a'};
void testAA(){
*aa='a';
cout<<"AA"<<endl;
}
};
class BB:public AA{
char *bb;
public:
void testBB(){
*bb='b';//如果没有这一句,那么下面main函数可以成功执行,并打印BB
cout<<"BB"<<endl;
}
};
int main(){
BB* aa=reinterpret_cast<BB*>(new AA());
aa->testBB();//能够调用子类函数,但是会崩溃。因为函数是属性类的,强制转化为子类后,就能调用子类的成语函数,但是如果成员函数会调用成员变量,并且成员变量没有初始化,那么大概率会崩溃。
aa->testAA();//能成功执行,无风险。
}
35、a:父类的成员对象能够被继承,但是如果在子类也声明一个同名变量,则子类会保存一个和父类同名的成语,引用哪一个成语需要看声明的变量。
b:不使用虚基类继承两个包含同一个成语变量的类会编译失败。
#include <iostream>
#include <stdlib.h>
class A
{
public:
int a=0;
};
class B :public A
{
public:
int a=1;
};
class C :public A
{
public:
};
class D :public A
{
public:
};
class E :public C, public D
{
public:
};
int main()
{
using namespace std;
A a;
cout << a.a << endl;//0
B b;
cout << b.a << endl;//1
A *aa = new B();
cout << aa->a << endl;//0
cout << ((B*)aa)->a << endl;//1
delete aa;
B *bb = new B();
cout << bb->a << endl;//1
delete bb;
E *e = new E();
//cout << e->a << endl;//编译失败,提示E::a不明确,可能是C也可能是D,需要虚基类
C *c = (C*)e;
cout << c->a << endl;//0,编译成功,不调用E.a的时候就没问题
delete e;
system("pause");
return 0;
}
B的内存分布:
class B size(8):
+---
0 | +--- (base class A)
0 | | a
| +---
4 | a
+---
E的内存分布:
class E size(8):
+---
0 | +--- (base class C)
0 | | +--- (base class A)
0 | | | a
| | +---
| +---
4 | +--- (base class D)
4 | | +--- (base class A)
4 | | | a
| | +---
| +---
+---
36、类的内存分布
a:空类大小为1,可以认为编译期为了防止大小为0的变量故意插入的值,没有意义,当类有成员时这个值就不会插入。
b:包含虚函数的类会有虚函数表指针,大小为4字节,继承虚函数时也会继承虚函数表vftable指针vfptr,所以子类也会加上这4个字节,继承自多少个抽象类就加上多少个4字节。
c:虚继承时父类大小不会有影响,子类大小会增加4字节,表示指向虚基类vbtable的指针vbptr
class A1
{
virtual void a()
{
}
};//4bytes
class A2
{
virtual void a()
{
}
};//4bytes
class B1 : public A1, virtual public A2
{
};//12bytes
B1内存分布:
class B1 size(12):
+---
0 | +--- (base class A1)
0 | | {vfptr}
| +---
4 | {vbptr}
+---
+--- (virtual base A2)
8 | {vfptr}
+---
B1::$vftable@A1@:
| &B1_meta
| 0
0 | &A1::a
B1::$vbtable@:
0 | -4
1 | 4 (B1d(B1+4)A2)
B1::$vftable@A2@:
| -8
0 | &A2::a
vbi: class offset o.vbptr o.vbte fVtorDisp
A2 8 4 4 0
ps:
VS2015打印内存方法
1、D:\workhome\chromium\chromium\vs2015\Common7\Tools\VsDevCmd.bat
2、cl /d1 reportSingleClassLayoutB1 "c:\Users\administrator\documents\visual studio 2015\Projects\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp"
37,typename含义和用法
typename只能用在模板中,用于区分命名空间和类名,让编译器确定此关键字声明的对象是类型,或者此关键字加双冒号的变量是类成员。
例如:
1:和class一样,class可以用来定义类,也可用作模板参数类型,而typename只能用作参数类型
template <typename T> class sp
{
public:
template <typename U> sp(U *other);
}
2;区分命名空间
typename T::iterator * iter;