C++基础_总结
(1)多态性都有哪些?(静态和动态,然后分别叙述了一下虚函数和函数重载)
多态分为两种:静态和动态。静态主要包括函数重载和模板;动态主要是依靠虚函数实现的。
静态联编:重载函数不加virtual关键字,就根据指针类型去执行
动态联编:加virtual关键字,运行时候根据具体类型执行不同对象的函数,变现成多态
函数重载:主要是在同一个类的作用域内,主要是通过参数类型或参数个数的不同,或(const修饰函数(其实是修饰的this指针))定义同一个函数名的不同功能,在调用的时候根据传递参数的具体情况,选择合适的重载函数。(注意:返回值不同,不是重载)。以运算符重载为例,完成运算符重载需要三个步骤:1、承认操作符重载是一个函数,写出函数名称;2、根据操作数,写函数参数的个数(一般成员函数法会比友元函数少一个参数(隐含this指针));3、确定返回值(引用,指针,元素)并实现函数业务;
模板:模板的本质是类型的参数化;包括函数模板和类模板:
函数模板的实现机制是:C++编译器对函数模板进行了二次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。
函数模板和普通函数的区别:
函数模板不允许自动类型转化,严格的类型匹配;普通函数能够进行自动类型转化,在不失精度的前提下。
1、函数模板可以像普通函数一样被重载
2、C++编译器优先考虑普通函数
3、如果函数模板可以产生一个更好的匹配,那么选择模板(比如在使用普通函数导致精度缺失的时候)
4、可以通过空模板实参列表的语法限定编译器只通过模板匹配:max<>(a,b);
类模板主要解决多个类功能一致,但数据类型不同的问题;
模板类派生普通类,需要具体化模板类,C++编译器需要知道父类的数据类型,要知道父类占内存大小,才能在派生类中通过初始化参数列表,初始化父类对象;
模板类派生模板类,父类不需要具体化模板类,只在调用的地方利用模板参数列表具体化参数类型即可。
模板类中的static关键字,不同的调用类型,static关键字属于不同的类,这是由模板的实现机制决定的。
模板类的函数重载:成员函数在参数,作用域,返回值处要使用模板参数列表<T>;友元类属于全局函数,不需要类的作用域。
基于虚函数的多态:表现形式是同样的调用语句多种不同的表现形态;
实现多态的三个条件:继承、同名虚函数的重写、父类指针指向子类对象。
C++编译器实现多态的方法:
1、 C++编译器为每个类定义对象时,提前布局vptr指针;
2、 通过vptr指针访问虚函数表
3、 当有虚函数调用时,找到虚函数的入口地址来进行动态的值绑定
多态的原理知识点:
效果;成立条件;C++多态的实现方法(vitual关键字告诉编译器这个函数要支持多态);多态的理论基础:静态联编和动态联编;实现多态的基础手段:函数指针做函数参数(基类指针指向子类对象)
多态原理探究:
1、 当类中声明虚函数时,C++编译器在类中生成一个虚函数表
2、 虚函数表是一个存储类成员函数指针的数据结构
3、 虚函数表是由编译器自动生成与维护的
4、 Virtual成员函数会被编译器放入虚函数表中
5、 当存在虚函数时,每个对象中都有一个指向虚函数表的vptr指针
C++编译器为父类对象和子类对象提前布局vptr指针,当进行父类指针做函数对象的函数调用时,C++编译器不需要区分父类指针指向子类对象或父类对象,而是在父类指针中找对象的vptr指针,vptr指针一般作为类对象的第一个成员,并指向虚函数表中的虚函数。
(2) 动态绑定怎么实现?(就是问了一下基类与派生类指针和引用的转换问题)
多态;
只有指定为虚函数的成员函数才能进行动态绑定
必须通过基类类型的引用或指针进行函数调用
因为每个派生类对象中都拥有基类部分,所以可以使用基类类型的指针或引用来引用派生类对象。另外,可以看一下动态类型转换。
(3) 类型转换有哪些?(四种类型转换,分别举例说明)
Static_cast:静态类型转换,C语言基本数据类型隐式类型转换可以的,他都可以;
Reinterpret_cast: 不同类型指针类型强制转换:
char* p1 = "hello"; int* p2 = NULL; p2 = reinterpret_cast<int*>(p1);
dynamic_cast:继承中多态时候的类型识别,子类对象传给父类指针后,可对父类指针进行动态类型转换:
Dog *pDog = dynamic_cast<Dog*>(base); if(pDog != NULL) { 转换成功; }
const_cast: 去除变量的只读属性(去除const属性)
void printBuf(const char* p) { char* p1 = NULL; p1 = const_cast<char*>(p); p1[0] = 'z'; //通过类型转换就可以改变指针指向的内存空间的值 } char buf[] = "afdasdf"; //栈 中分配内存 printBuf(buf); // ok char *my_p = "fsjljsf"; //字符常量,在 全局数据区 ,本身就不能改变; printBuf(my_p); //error
- 首先字符串是在静态常量区分配的内存,然后指针my_p在栈里分配的内存,然后将指针指向”abacd”所在的内存块。指针存放的是"fsjljsf"的地址,而这个地址所在内存是在静态常量区中,是不可改变的。
- char buf[]数组是在栈中,数组栈中分配了内存,是局部变量,也就是说,由于char buf[]是分配了内存的,所以这里只是将"afdasdf"复制到char buf[]在栈中分配的内存中去,是可读可写的。这里就和指针区别出来了,指针是不分配内存的,指针指向的是一块静态常量区中的内存。
(4) 操作符重载(+操作符),具体如何去定义,?(让把操作符重载函数原型说一遍)
返回类型 operator 操作符(形参表);
声明:Complex& operator+(complex& obj);
定义:Complex& operator+(complex& obj){ }
完成运算符重载需要三个步骤:1、承认操作符重载是一个函数,写出函数名称;2、根据操作数,写函数参数的个数(一般成员函数法会比友元函数少一个参数(隐含this指针));3、确定返回值(引用,指针,元素)并实现函数业务;
注意前置++和后置++的区别(前置返回的是*this,本身的引用;后置返回的是一个临时变量,是一个元素,在STL的map关联容器删除时,使用到了it++的技术,迭代器先自增,然后返回自增前的临时变量)
(5) 内存对齐的原则?(原则叙述了一下并举例说明)
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。结构体是数据成员长度的整数倍。
3、结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
(6)模版怎么实现?
模板的本质是类型的参数化;包括函数模板和类模板:
形式:template<typename T>或template<class T>
函数模板的实现机制是:C++编译器对函数模板进行了二次编译,在声明的地方对模板代码本身进行编译,通过模板实参列表<>在调用的地方对参数替换后的代码进行编译。
函数模板是严格的类型匹配,不能进行隐式类型转换;当和普通函数都满足时优先普通函数,只有在普通函数导致精度缺失时才使用函数模板。
类模板主要解决多个类功能一致,但数据类型不同的问题;
模板类派生普通类:需要具体化模板类,C++编译器需要知道父类的数据类型,要知道父类占内存大小,才能在派生类中通过初始化参数列表,初始化父类对象;
模板类派生模板类:父类不需要具体化模板类,只在调用的地方利用模板参数列表具体化参数类型即可。
(7) 指针和const的用法?(就是四种情况说了一下)
容易混淆:
1、const int *p; //指向const对象的指针;可以随后对指针赋值(const和非const的值都可以),但指针指向的值*p不能被改变。*p=2; error 2、int *const p = &a; //const指针p指向整形变量;p是const,必须在定义的时候初始化,不能将变量地址赋给p。p = &b; //error 3、const int a = 3; //a是const对象,不可改变其值 const int* const p = &a; //const指针p指向int类型的const对象 p = &b和 a = 2;都是非法的。
4、指针和typedef一起使用时,const修饰的是指针
typedef string* pstring; const pstring cstr; // 等价于 string* const cstr; //const指针cstr指向string类型对象 好处:pstring p,q; //p和q都是指针; string *p,q; //p是指针,q不是。
(8) 虚函数、纯虚函数、虚函数与析构函数?(纯虚函数如何定义,为什么析构函数要定义成虚函数)
虚函数:定义一个函数为虚函数,不代表函数不可被实现的函数,而是为了允许用基类的指针来调用子类的这个函数。C++处理虚函数是动态联编,不在编译时候确定,而是在运行时刻确定调用基类函数还是派生类函数。
纯虚函数:纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion1()=0
析构函数定义为虚函数,将调用相应对象类型的析构函数,因此,如果基类指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
(9)内联函数(讲了一下内联函数的优点以及和宏定义的区别)
内联函数避免了函数调用的开销,而是在调用点“内敛的展开”(只保留了return后面的部分小代码)。内联函数一般在声明处定义,放在头文件里。
(1) 在预编译时期,宏定义在调用处执行字符串的原样替换。在编译时期,内联函数在调用处展开,同时进行参数类型检查。(把int赋给指针,而不是地址,参数类型检查到就报错)
(2) 内联函数首先是函数,可以像调用普通函数一样调用内联函数。而宏定义往往需要添加很多括号防止歧义,编写更加复杂。
(3) 内联函数可以作为某个类的成员函数,这样可以访问类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了(无法将this指针放在合适位置)。
(10) const和typedef(主要讲了const的用处,有那些优点)
指针和typedef一起使用时,const修饰的是指针
typedef string* pstring;
const pstring cstr; // 等价于 string* const cstr; //const指针cstr指向string类型对象
好处:pstring p,q; //p和q都是指针;
string *p,q; //p是指针,q不是。
(11) 排序算法有哪些?快速排序怎么实现的?最好时间复杂度,平均时间复杂度
冒泡,插入:平均和最坏O(n*n)
希尔排序: 减小增量排序
堆 , 归并: 平均最坏O(nlogn)
快排: O(nlogn) 最坏:O(n*n)
(12) 链接指示:extern “C”(作用)
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般之包括函数名。比如说你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL输出(Export)的函数,你需要用extern "C"来强制编译器不要修改你的函数名。
(13) c语言和c++有什么区别?(大体讲了 一下,继承、多态、封装、异常处理等)
继承:代码复用;
封装:隐含this指针,默认class(private)与struct(public);
函数重载;使用链接提示extern “C”的原因,编译后C和C++命名方式不同。
多态;掌控未来人写的代码。
异常处理:C++有try…catch捕捉异常
C语言通过longjmp()发出异常,转到setjmp()处理异常。
在使用 setjmp() 初始化 jmpb 后,返回值是0。其后任意地方使用 longjmp() 函数跳转会 setjmp() 函数的位置,longjmp() 的第一个参数便是 setjmp() 初始化的 jmpb,;longjmp() 函数的第二个参数是传给 setjmp() 的第二次返回值非0。
(1) qt类中的一些继承关系?
QObject->
QWidget(QAbstractButton, QDialog, QFrame, QMainWindow,QMenu,QLineEdit,QWebEngineView)
QAbstractButton(QRadioButton, QPushButton)
QDialog(QFileDialog, QMessageBox)
QFrame(QLable, QTextEdit,QTableWidget)
QWebEngineView(QWebEngineSettings, page, runJavascript)
(2) qt的信号与槽机制?
A对象声明信号(signal),;B对象实现与之参数相匹配的槽(slot),通过调用connect进行连接,状态改变时A对象使用emit把信号带上参数发射出去,B对象的槽会就接收到响应。
信号槽机制有一些特点:
1. 类型安全:只有参数匹配的信号与槽才可以连接成功(信号的参数可以更多,槽会忽略多余的参数)。
2. 线程安全:通过借助QT自已的事件机制,信号槽支持跨线程并且可以保证线程安全。
3. 松耦合:信号不关心有哪些或者多少个对象与之连接;槽不关心自己连接了哪些对象的哪些信号。这些都不会影响何时发出信号或者信号如何处理。
4. 信号与槽是多对多的关系:一个信号可以连接多个槽,一个槽也可以用来接收多个信号。
(3) qt有那些类,控件?
QTcpServer
QTCPSocket
QWebSocket
QWebSocket
QNetworkAccessMannger
(5) strcpy函数的编写?
char* strcpy(char* des , const char* src);
(6) 数据结构中二叉树的非递归遍历?
利用栈结构(先,后序遍历)
(7)c++中四种类型转换机制?
static_cast; reinterpret_cast; dynamic_cast; const_cast;
(8)继承机制中对象之间是如何转换的?
继承中可以用子类对象初始化父类对象,调用父类对象的copy构造函数,将子类对象赋值给copy构造函数参数中父类对象的引用,然后,利用子类对象构造函数初始化列表中父类部分对父类对象进行初始化。表面上来看是子类对象转化为父类对象。是自动转化的.
父类到子类强制类型,无虚函数是一个随机值;有虚函数报错,父类转到子类无法转换。
(9) 继承机制中引用和指针之间如何转换?
不管是对象还是指针子类都可以自动转为父类,调用父类的函数。
基类和派生类指针转换方法dynamic_cast:如果基类指针确实是指向了一个派生类对象,此运算符会传回转换后的派生类指针,否则,返回空指针。多了一个是否转换成功的类型检查,比强制转换要好点儿。
C继承了B,B继承了A:
A* pA = new B;
那么
B* pB = dynamic_cast<B*>pA; // OK.
C* pC = dynamic_cast<C*>pA; // Fail.
如果说你不能确定这种包含关系,最好使用dynamic_cast.
实际上可以把dynamic_cast看做是强制类型转换的一个子集,看成是更严格检查的强制类型转换,因为'更严格'所以能够检查出来错误.
pbase = new derived;父类可以成功转化为子类。(调用子类的虚函数)
(10) 虚函数,虚函数表里面内存如何分配?(这个考前看过了,答的还不错)
C++编译器提前布局一个Vfptr指向虚函数表中虚函数的入口地址;构造类对象时,vfptr指针作为其对象的第一个成员属性。
属性->命令行-> /d1 reportAllClassLayout 可查看内存布局和虚函数表的情况。
VS所带编译器是把vfptr放在了内存的开始处(0地址偏移),然后再是成员变量;
下面生成了虚函数表,紧跟在&Base1_meta后面的0表示,这张虚表对应的虚指针在内存中的分布,下面列出了虚函数,左侧的0是这个虚函数的序号,这里只有一个虚函数,所以只有一项,如果有多个虚函数,会有序号为1,为2的虚函数列出来。
多态内部原理:vptr指针的分布初始化。子类对象构造函数的调用顺序,先父后子。
当创建一个含有虚函数的父类的对象时,编译器在对象构造时将虚表指针指向父类的虚函数;同样,当创建子类的对象时,编译器在构造函数里将虚表指针(子类只有一个虚表指针,它来自父类)指向子类的虚表(这个虚表里面的虚函数入口地址是子类的)。
所以,如果是调用Base *p = new Derived();生成的是子类的对象,在构造时,子类对象的虚指针指向的是子类的虚表,接着由Derived*到Base*的转换并没有改变虚表指针,所以这时候p->VirtualFunction,实际上是p->vfptr->VirtualFunction,它在构造的时候就已经指向了子类的VirtualFunction,所以调用的是子类的虚函数,这就是多态了。
(11)如何实现只能动态分配类对象,不能定义类对象?(这个牛客上的题目,我把如何只能动态分配和只能静态分配都讲了一下)
动态分配类对象:就是使用运算符new来创建一个类的对象,在堆上分配内存。
静态分配类对象:就是A a,由编译器创建类对象,在栈上分配内存。
1)动态分配类对象
把类的构造函数和析构函数设为protected属性。类对象不能访问,但是派生类可以继承,也可以访问。
同时,创建create和destroy两个函数,用于创建类对象。
(create函数设为static,原因是,创建对象的时候A *p=A::create().只有静态成员函数才能有类名直接访问)
2)静态分配类对象
重载new和delete操作符,并声明为私有的。
void* operator new(size_t t) //函数的第一个参数与返回值都是固定的
{ }
void operator delete(void *ptr) //重载了new,就需要重载delete
{ }
(12) stl有哪些容器,对比vector和set?
vector动态数组;set关联容器;
(13) 红黑树的定义和解释?
红黑树,是一种二叉查找树,在每一个节点上增加一个存储位表示节点的颜色(红或黑),然后通过一些限制(它的性质)保证没有一条路径会比其他路径长出两倍,因此是接近平衡的。时间复杂到O(logn),平衡条件没有AVL严格,提高了性能。
RB-Tree的性质
1、根和叶子两头为黑,中间非红即黑;
2、如果节点为红,其子节点必须为黑。
3、任一节点至NULL(树尾端)的任何路径,所含之黑节点数必须相同。
(14) const关键字的作用?(const成员函数,函数传递,和define的区别)
1、常成员函数不能更新类的成员变量(why),也不能调用该类中没有用const修饰的成员函数,只能调用常成员函数。常成员函数可以被其他成员函数调用。
常成员函数:准确的说const是修饰this指向的对象
classA{
public:
f(int)const;
};
这里f函数其实有两个参数,第一个是A* const this, 另一个才是int类型的参数,但如果我们不允许f改变this指向的对象呢?因为this是隐含参数,const没法直接修饰它,就加在函数的后面了。
const修饰*this是本质,至于说“表示该成员函数不会修改类的数据。否则会编译报错”之类的说法只是一个现象,根源就是因为*this是const类型的.
被mutable修饰的变量,将永远处于可变的状态,
即使在一个const函数中。
2、用于函数重载,加const的重载函数的变量不能修改其值。
3、参数传递(strcpy的源地址 const char*):const形参,不能改变其值,不能当左值。
4、const有数据类型,而宏定义是没有的。那么编译器可以对const进行类型安全检查,对#define只进行简单的字符串替换,没有安全类型检查,就可能出现运行时的错误。
(15) 静态成员函数和数据成员有什么意义?
对于特定类类型的全体对象而言,访问一个全局变量有时是必要的。然而,全局变量会破坏封装:对象需要支持特定类抽象的实现。如果对象是全局的,一般的用户代码就可以修改这个值。鉴于此,类可以定义 类静态成员,而不是定义一个可普遍访问的全局对象。
使用static成员的优点:
(1)避免命名冲突:static成员的名字在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
(2)可以实施封装:static成员可以是私有成员,而全局对象不可以。
(3)易读性:static成员是与特定类关联的,可显示程序员的意图。
static成员函数没有this形参,它可以直接访问所属类的static成员,但不能直接使用非static成员。主要用于操作static成员。
(16) 模版特化的概念,为什么特化?
不能直接使用泛型模板展开实现,这时就需要针对某个特殊的类型或者是某一类特殊的类型,而实现一个特例模板————即模板特化。通常会使用到模板特化的有(应该也只能有)类模板和函数模板。
a. 类模板特化
在已有类模板
template <class T>
class stack { //...// };
定义时,可能考虑到一些特定的类型T,数据的存储可能和通用模板不一样,因而可以像如下定义一个特例化模板:
template < >
class stack<bool> { //...// };
b. 函数模板的特化
template <class T>
T max(const T t1, const T t2)
{
return t1 < t2 ? t2 : t1;
}
如上已有的模板定义可能在针对一个指针类型的参数时,工作将可能会不正常,具体到字符串指针类型时,可能就需要下面的特例化模板定义:
template < >
const char* max(const char* t1,const char* t2)
{
return (strcmp(t1,t2) < 0) ? t2 : t1;
}
这样才能使max("aaa", "bbb");的调用更如人意(通常没有人想比较两个常量字符串存储的地址的大小)。
(17) explicit是干什么用的?
用于修饰一个参数的构造函数(注意:如果有多个参数,只有一个参数没有默认值,也是一个参数的范畴)
防止隐式类型转换;
定义有参构造函数: CExample(int iFirst, int iSecond = 4);
CExample objOne; //调用编译器默认的无参构造函数
CExample objTwo(12, 12); //调用有两个参数的构造函数
CExample objThree(12); //同上,传一个参数是因为该构造函数的第二个参数有默认值
CExample objFour = 12; //执行了隐式转换(由int转化为CExample类型),等价于CExample temp(12);objFour(temp);注意这个地方调用了编译器为我们提供的默认复制构造函数
定义显示声明的有参构造函数: explicit CExample(int iFirst, int iSecond = 4);
那么CExample objFour = 12; 这条语句将不能通过编译。在vs05下的编译错误提示如下
error C2440: 'initializing' : cannot convert from 'int' to 'CExample'
Constructor for class 'CExample' is declared 'explicit'
(18) strcpy返回类型是干嘛用的?
支持链式编程;
(19) 内存溢出有那些因素?
使用内存未回收,单次使用内存过大,导致申请内存失败。
数据库单次读取数据过大,可采用分页方式查询。
大循环中重复产生新的对象实体;
代码中的死循环;
List,map等集合使用完后,没有clear;
(20) new与malloc的区别,delet和free的区别?
malloc, free 是c的库函数,只会分配内存大小,不会调用构造函数,析构函数
new, delete C++关键字,操作符,不仅可以分配内存大小,还可以调用构造函数,析构函数
在执行基本类型,数组(其中存储的是基本类型)操作时,可以混用!
(21) 为什么要用static_cast转换而不用c语言中的转换?
基本类型转换,C语言可以隐式转换的,static_cast都可以;
另外,static_cast不能进行无关类型(如非基类和子类)指针之间的转换,编译器进行类型检查后报错,而C语言强制类型转换可以转换,不安全。
(22) 异常机制是怎么回事?
正常情况下,我们可以采用函数返回值来判断程序是否正常运行;但C++构造函数没有返回类型,无法通过返回值来报告运行状态,所以只能通过一种非函数机制的途径,即:异常机制,来解决构造函数的出错问题!!!
1、将可能抛出异常的程序段放在try中。如有异常通过throw操作创建一个异常对象并抛掷;
2、try的保护段没有异常,try后catch不执行,直到try后跟随的随后一个catch后面的语句继续执行下去。
3、catch子句按照try后顺序执行,捕获或继续抛出异常。
4、异常是严格按照类型匹配,不会隐式类型转换。
关于异常机制的优势:
1、C++异常处理机制使得 异常引发 和 异常处理不必在同一个函数,底层更注重解决具体问题,而不必过多考虑异常的处理,上层调用者可以在适当位置设置 对不同类型异常的处理;
2、异常是针对抽象编程中一系列错误处理的机制,C++不能借助函数机制,因为栈结构先进后出,依次访问,但异常处理要跳级处理,跨越函数。
异常的注意事项:
异常变量的生命周期问题: 从try到异常throw前,其中的对象会自动逆序析构,这一过程叫,栈解旋。那么在try中存在异常,而程序员又动态的申请了内存,catch到异常后需要先delete,释放这部分内存,再通过throw将异常抛出。
// 第一步,分配原始内存,若失败则抛出bad_alloc异常
try {
// 第二步,调用构造函数构造对象
p = new T;
}
catch(...) {
delete []p; // 释放第一步分配的内存
throw; // 重抛异常,通知应用程序
}
(23) 迭代器删除元素的会发生什么?
一般迭代器删除元素会使迭代器失效,但不同的容器情况不同:
vector执行删除操作后,被删除元素的迭代器及其后面元素对应的迭代器都将失效。
deque删除操作:
A.在deque首、尾删除元素只会使被删除元素对应的迭代器失效;
B.在其他任何位置的删除操作都会使得整个迭代器失效。
关联容器map、set和list而在删除元素时,仅仅会使得指向被删除的迭代器失效。
(24) 必须在构造函数初始化式里进行初始化的数据成员有哪些?
常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
组合对象,需要初始化的数据成员是父类的对象(继承时调用基类构造函数)
注意:因为static属于类并不属于具体的对象,所以 static const成员也不允许在类内初始化的。
(25) 类的封装:private,protected,public
单个类访问控制:
private类内;protected类内或子类内;public类外。
继承关系中的访问控制:
Private继承:父类所有成员属性和方法,对子类都是private;
Public继承:父类中所有成员属性和方法,对子类都保持原有的方式不变;
Protected继承:父类中public变pretected,pretected和private保持不变。
继承中同名成员变量处理方法:
1、子类成员变量 与 父类成员变量同名时 子类依然从父类继承同名成员变量
2、 通过 域作用符 区别子类和父类的同名成员变量(子类中使用父类的域作用符)
3、同名成员存储在内存中不同位置。
child.b = 1; //默认使用子类自己的b
child.parent::b = 2; //子类调用父类的b(使用了父类的域作用符)
派生类中的static关键字:父类中的static成员,满足派生类的访问控制
静态成员变量 类外定义初始化,提醒编译器分配内存
类中不声明,默认为私有。构造函数声明为一般公有(单例除外)(结构体中默认为公有)
(26) auto_ptr类:
std::auto_ptr<Teacher> myPtr;
补充:设计模式:单例和工厂