函数默认值参数、inline,函数重载、引用、new 和 malloc、作用域
inline只适用于函数体内代码简洁的函数使用,不能包含复杂的结构控制语句(循环 or 递归)内联函数仅仅是一个对编译器的建议,建议将 inline 放在头文件中(内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义)小心代码膨胀
new(操作符)—>调用构造----->调用operator new(函数)—>调用malloc(函数)
new失败抛异常(符合C++规范)
malloc失败返回NULL
delete(操作符)–>调用析构—>调用operator delete(函数)—>free(函数)
是什么、怎么用、基本原理
static_cast和dynamic_cast效果一样,都安全;
你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象,如果是,static_cast和dynamic_cast都能成功;如果不是static_cast能返回,但是不安全,可能会出现访问越界错误,而dynamic_cast在运行时类型检查过程中,判定该过程不能转换,返回NULL。
编译时类型检查
void Func()
{
int i;
float f;
f = (float)i;
f = static_cast<float>(i);
想得到的类型 = static_cast<目标类型>(目标)
}
用于父类子类之间的转换,存在两种形式即上行转换(子类到父类)和下行转换(父类到子类),前者安全,(因为子类总是包含父类的数据成员和函数成员,因此从子类转换到父类的指针的对象可以没有任何顾虑的访问其成员,)至于下行转换不安全,static_cast 转换成功返回转换后的正确类型,转换失败不报错,不反回 NULL
运行时类型检查
dynamic_cast
是什么、怎么用、基本原理
概念、赋值、存储方式、sizeof、初始化、传参、函数指针,数组指针,指针数组、
数组:是用于存储多个相同类型数据的集合
指针:指针相当于一个变量,但是它和一般变量不一样,它存放的是其他变量在内存中的地址
同类型指针变量可以相互赋值,而数组只能一个一个赋值或拷贝
数组:数组在内存上是连续存储的,开辟一块连续的内存空间,按照下表进行访问数组元素,多维数组也是按照一维数组进行组织的
指针:指针灵活,可以指向任意类型的数据,指针的类型说明了它所指地址空间的内存
数组
指针
指针大小与平台相关
指向不明确的指针变量,
多态是面向对象的一个基本属性,包括静态多态(编译阶段)和动态多态(运行阶段),静态多态主要是指函数参数不同产生的多态性,是在编译阶段可以识别的一种多态机制,而运行时多态则主要用于基类指针指向派生类对象时,可以通过基类指针直接调用派生类的对象函数,当然这种多态是通过虚函数实现的。
虚函数的目的就是通知系统在函数调用时能够自动识别对应的类对象类型,从而能够根据指针所指类型调用对应的类对象,实现函数调用时的多态性。对于析构函数而言,同样适用于上述规则。如果析构函数不是虚函数,那么在调用该函数时(对象被删除时)则只会调用当前对象对应的类的析构函数,这对于直接定义的对象是没有什么影响的,但是对于使用基类指向派生类的指针而言,因为基类指针实际上是基类类型,所以析构时自然只会调用基类的析构函数,这就可能产生内存泄漏(因为派生类的析构函数不被调用)。所以如果确定程序中有基类指针指向派生类的问题,则必须将基类的析构函数指定为虚函数,如此才能确保 NEW 出来的对象被正确的 DELETE。
构造函数&析构函数与虚函数的联系
前置 ++ 返回引用,后置 ++ 返回 const 对象 & 后置 ++ 会生成临时对象
++a 表示取 a 的地址,增加他的内容,然后把值放入寄存器中
a++ 表示取 a 的地址,把他的值装入寄存器,然后增加内存中 a 的值(也就是说,操作时用的时寄存器中的值,增增前的值)
#define、const
extern C 主要作用就是为了让 C++ 代码调用其他 C 语言代码,加上 extern C 后,会指示编译器将这部分代码按照 C 语言的编码格式进行编译,
是什么、基本实现
RTTI 运行时类型识别,RTTI 是为了让程序在运行期间能根据基类的指针或引用来获得该指针或引用所指对象的实际类型
class type_info
{
public:
virtual ~type_info();
bool operator==(const type_info&)const;
bool operator!=(const type_info&)const;
bool before(const type_info&)const;
const char* name()const;
private:
type_info(const type_info&);
type_info& operator=(const type_info&);
};
参数压栈顺序从右至左,
由于 strcpy 并没有保证目标字符串大小肯定大于源字符串大小,因此引入了一个 strcpy_s,其函数原型
car* strcpy_s(char* dst, int size, const char* src)
char* strcpy(char* dst, const char* src){
if(dst == NULL || src == NULL){
return NULL;
}
if(dst == src){
return dst;
}
char* tmp = dst;
while((*dst++ = *src++) != "\0");
return tmp;
}
注意
int strlen(const char* str){
assert(str != NULL)
int i = 0;
int count = 0;
while(str[i] != "\0"){
count++;
i++;
}
return count;
}
int strlen(const char* str){
const char* p = str;
while(*p != "\0"){
p++;
}
return p - str;
}
int strlen(const char* str){
if(*str == "\0"){
return 0;
}
else{
return 1 + strlen(str + 1);
}
}
是什么、解决了什么问题、怎么用、执行顺序、与虚函数的关系
虚继承,解决从不同途径继承而来的同名的数据成员在内存中有着不同的拷贝造成的数据不一致问题,将共同基类设置为虚基类。这时从不同途径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射
解决了菱形继承中的二义性问题,也节省了内存,避免了数据不一致问题
class A
{
int g_val;
};
class B : virtual public A
{};
class C : public A
{}
class D : public B, public C
{};
首先执行虚基类的构造函数,多个虚基类的构造函数按照被继承顺序来构造
执行基类的构造函数,多个基类的构造函数按照被继承的顺序来构造
执行成员对象的构造函数,多个成员对象按照被声明的顺序来构造
执行派生类自己的构造函数
析构函数以构造函数相反的顺序执行
从虚基类直接或间接的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用,但只有建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次
优先级:
在一个成员的初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行
class Base
{
public:
double dou;
};
class Derived1 : public virtual Base
{
public:
double in;
};
class Derived2 : public virtual Base
{
public:
double on;
};
class A : public Derived1, public Derived2
{};
int main()
{
int i = 0xaabbccdd;
double a = 1, b = 2, c = 3, d = 4;
Base bobj;
bobj.dou = 1;
Derived1 d1obj;
d1obj.dou = 1;
d1obj.in = 2;
Derived2 d2obj;
d2obj.dou = 3;
d2obj.on = 1;
A aobj;
aobj.dou = 4;
aobj.in = 1;
aobj.on = 1;
cout << sizeof(bobj) << endl;
cout << sizeof(d1obj) << endl;
cout << sizeof(d2obj) << endl;
cout << sizeof(aobj) << endl;
return 0;
}
8
20
20
32
虚基类表中存放了虚基类成员在派生类内存空间中的偏移量
是什么、有什么作用、虚函数具体实现
简单来说,虚函数就是被 virtual 关键字修饰的成员函数,其作用为了实现多态特性,多态性就是将接口与实现分离;其实就是实现一种共同的方法,但因个体差异而采用不同的策略,虚函数主要有虚表实现
纯虚函数相当于基类只提供接口而不进行定义,在函数声明后面加上“=0”
例如:virtual void Eal() = 0;
纯虚函数只能在派生类中实现,因为它只提供了接口,而虚函数在派生类中可以覆盖也可以不覆盖,直接使用基类的实现
派生类函数重写基类函数
派生类函数屏蔽基类同名函数
static 成员函数不能被覆盖和隐藏
class Base
{
public:
virtual void func(float x)
{
cout << "Base::func(float)" << x << endl;
}
void gloub(float x)
{
cout << "Base::gloub(float)" << x << endl;
}
void hub(float x)
{
cout << "Base::hub(float)" << x << endl;
}
};
class Derived : public Base
{
public:
virtual void func(float x)
{
cout << "Derived::func(float)" << x << endl;
}
void gloub(float x)
{
cout << "Derived::gloub(float)" << x << endl;
}
void hub(float x)
{
cout << "Derived::hub(float)" << x << endl;
}
};
int main()
{
return 0;
Derived d;
Base *pb = &d;
Derived *pd = &d;
pb->func(3.14f); //覆盖
pd->func(3.14f); //覆盖
pb->gloub(3.14f); //非虚函数,直接调用基类函数
pd->gloub(3.14f); //隐藏了只能调用派生类函数
pb->h(3.14f); //Base::h(float) 3.14(非虚函数,直接调用基类函数)
pd->h(3.14f); //Derived::h(float) 3.14(隐藏)
}