C++ FAQ
空类
class A {
};
// sizeof(A) = 1
空类的大小之所以为1,因为标准规定完整对象的大小>0,否则两个不同对象可能拥有相同的地址,故编译器会生成1B占位符。
那么两个对象为什么不能地址相同呢?
There would be no way to distinguish between these two objects when referencing them with pointers.
空类中到底都有什么呢?
class A {
public:
A(); // 默认构造函数
A(const A&); // 拷贝构造函数
~A(); // 析构函数
A& operator=(const A&); // 赋值运算符
A* operator&(); // 取址运算符(非const)
const A* operator&() const; // 取址运算符(const)
};
仅仅声明一个类,不会创建这些函数。只有当定义类的对象时,才会产生。
多态和虚函数
面向对象的语言的特点就是封装、继承和多态。封装和继承都比较好理解,那么多态到底什么意思?
简单来说:不同对象接收相同的消息产生不同的行为。
C++中的多态分为静态多态(函数和运算符重载)和动态多态(继承和虚函数)。
定义虚函数f
,是为了用基类的引用或指针调用派生类的f
,最终调用哪个f
取决于传入的实参,即在运行时选择函数的版本,也就是所谓的动态绑定。
class Base {
public:
virtual void f() {
cout << "Base";
}
virtual void g() {}
private:
int i;
};
class Derived : public Base {
public:
virtual void f() { // 覆盖Base::f
cout << "Derived";
}
virtual void h() {}
private:
int j;
};
int main() {
Base* p = new Derived();
p->f(); // 调用派生类的f()
delete p;
return 0;
}
基类指针p
调用虚函数f
,f
作用的可能是基类对象,也可能是派生类对象,这就是多态(同样消息作用于不同类型对象产生不同的行为)的一种方式,即动态多态。
正因为编译器无法确定使用哪个虚函数,所以所有的虚函数必须定义,否则编译器会报错。
构造函数不能是虚函数,因为构造对象时必须明确知道其类型。如果是虚函数,调用时只需要提供接口,编译器无法知道你想构造继承树的哪个类型。
C++他爹Bjarne Stroustrup是这么说的:
A virtual call is a mechanism to get work done given partial information. In particular, "virtual" allows us to call a function knowing only an interfaces and not the exact type of the object. To create an object you need complete information. In particular, you need to know the exact type of what you want to create. Consequently, a "call to a constructor" cannot be virtual.
析构函数是虚函数,因为要确保执行相应对象的析构函数。如果基类指针指向派生类对象,会调用派生类的析构函数,然后调用基类的析构函数。
纯虚函数
与虚函数必须定义相反,纯虚函数无须定义(要定义必须在类的外部),含有纯虚函数的类是抽象基类。
抽象基类定义好接口,继承该类的其他类可以覆盖这个接口。
virtual void f() = 0; // 声明纯虚函数
之所以要引入纯虚函数,是因为很多时候基类产生对象是没有意义的。比如动物类可以派生出狗、猪等子类,但动物类生成对象毫无意义。
因此,不能创建抽象基类的对象,派生类必须覆盖(override)以定义自己的f
,否则派生类仍然是抽象基类。
重载&覆盖&重写
- 重载(overload):在类内部发生。函数名相同,参数个数、参数类型、参数顺序至少有一种不同。返回值类型可以相同,也可不同;
- 覆盖(override):覆盖基类的虚函数。函数名相同,参数相同,基类函数必须是虚函数;
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
};
struct D1 :B {
void f1(int) const override; // 正确:f1与基类中的f1匹配
void f2(int) override; // 错误:B没有形如f2(int)的函数
void f3() override; // 错误:f3不是虚函数
void f4() override; // 错误:B没有名为f4的函数
};
- 重写(overwrite):派生类的函数屏蔽了同名的基类函数:
派生类函数与基类函数同名,参数不同。不论基类函数是否为虚函数,都会被隐藏;
派生类函数与基类函数同名,参数相同。基类函数不为虚函数,会被隐藏;
static
C++中static
关键字用来声明类的成员:
- 类的静态成员变量或函数属于类而非对象,只有一份副本;
- 静态成员函数没有
this
指针,只能访问类的静态数据; - 静态成员函数不能定义为虚函数;
- 静态成员变量初始化
int Base::name = 0
如果不是在类中声明成员,还有下面用法:
- 隐藏作用:多文件编译时,定义的全局变量和函数都是整个工程可见的,只要使用时加上
extern
关键字即可。如果加上static
关键字,那么该变量或函数就变为仅当前文件可见,这样我们可以在不同文件中定义同名的变量或函数而不用担心冲突。 - 全局生存期:
static
变量存储在静态数据区,默认值为0,只被初始化一次,即使作为局部变量,生存期也为整个程序,但作用域与普通变量相同,退出函数后即使变量存在,但不能使用。
const
- 定义const对象:一旦创建其值不能改变,故const对象必须初始化。
const int bufSize = 512;
int const bufSize = 512; // the same as the previous one
由于const对象默认只在文件内有效,所以如果要在文件间共享:
// file1.cpp定义并初始化
extern const int bufSize = 512;
// file1.h可以仅声明,不初始化
extern const int bufSize;
- 常量指针(const pointer):指针本身(存在指针中的地址)不可变。
int num = 0;
int* const p = # // p将一直指向num
- 指向常量的指针(pointer to const):指针指向的对象不可变。
const double pi = 3.14;
double* p = π // 错误,p是一个普通指针
const double* p = π // 正确
*p = 4.1; // 错误,不能改变*p的值
- 修饰成员函数
class A {
void f() const; // 不能改变数据成员,const对象不能调用非const成员函数
};
- 修饰类对象
class A {
void f1();
void f2() const;
};
const A obj; // obj为常量对象,任何成员都不能被修改,任何非const成员函数都不能被调用
obj.f1(); // 错误
obj.f2(); // 正确
const A* obj = new A();
obj->f1(); // 错误
obj->f2(); // 正确
- 转为非const
const char* pc; // pc指向内容不可变
char* p = const_cast<char*>(pc); // 正确,但是通过p写值是未定义行为
类型转换
类型转换分为隐式转换和显式转换。
显式转换有四种:
static_cast
没有底层const都可以,使用比较普遍。
基类->派生类:不安全
主要执行非多态转换,代替C中的转换。
void* p = &d;
double* dp = static_cast<double*>(p);
dynamic_cast
运行时类型检查,
将基类指针或引用安全转换为派生类的指针或引用:
// type是类,且有虚函数
dynamic_cast<type*>(e); //e是指针
dynamic_cast<type&>(e); //e是左值
dynamic_cast<type&&>(e); //e不是左值
const_cast
改变底层const。
常量指针转为非常量指针。
const char* cp;
char* q = static_cast<char*>(cp); // wrong, static_cast不能用于底层const
char* p = const_cast<char*>(cp); // true
reinterpret_cast
比较危险,不太用。处理无关类型转换,重新解释对象的比特模型。
new/delete/malloc/free
new/delete
是C++运算符,需要编译器支持,所以不需要指定大小,返回相应对象类型的指针,分配失败会抛出std::bad_alloc
异常,new
会调用operator new()
申请内存(用malloc
实现),调用构造函数初始化成员变量,返回相应指针,delete
先调用析构函数,再调用operator delete()
函数释放内存(用free
实现);
malloc/free
是库函数,不由编译器控制,需要显式指出大小,返回void*
,需要强制类型转换,分配失败返回NULL
指针,无法完成对象的构造和析构。
智能指针
new
完后没有delete
,内存泄漏。为了减少程序员的负担,引入智能指针:
shared_ptr
允许多个指针指向同一个对象。通常与make_shared
函数结合食用:
shared_ptr<string> p = make_shared<string>(10, '9');
实现方式一般是reference counting,在堆上申请资源并返回指针后,在堆上申请一个共享的引用计数器,每来一个指针指向该对象,++计数器。当计数器为0时,会自动释放指向的对象。
2个指针成员,一个指向对象,一个指向计数器
面试有可能被要求手撕一个:
template<class T>
class mySharePtr {
public:
mySharePtr() :refCnt(nullptr), ptr(nullptr) {}
mySharePtr(T* res) :refCnt(nullptr), ptr(res) {
add();
}
mySharePtr(const mySharePtr<T>& p) :refCnt(p.refCnt), ptr(p.ptr) {
add();
}
virtual ~mySharePtr() {
remove();
}
// lvalue is assigned, --counter
mySharePtr<T>& operator=(const mySharePtr<T>& that) {
if (this != &that) {
remove();
this->ptr = that.ptr;
this->refCnt = that.refCnt;
add();
}
return *this;
}
bool operator==(const mySharePtr<T>& other) {
return ptr == other.ptr;
}
bool operator!=(const mySharePtr<T>& other) {
return !operator==(other);
}
T& operator*() const {
return *ptr;
}
T* operator->() const {
return ptr;
}
int numRef() const {
if (refCnt) {
return *refCnt;
}
else {
return -1;
}
}
protected:
// if null, create counter = 1, else ++counter
void add() {
if (refCnt) {
++(*refCnt);
}
else {
refCnt = new int(1);
}
}
// --counter, if counter = 0, free memory
void remove() {
if (refCnt) {
--(*refCnt);
if (*refCnt == 0) {
delete refCnt;
delete ptr;
refCnt = nullptr;
ptr = nullptr;
}
}
}
private:
int* refCnt;
T* ptr;
};
unique_ptr
看名字就知道,独占对象。
指针和引用
引用只是一个别名,不是一种数据类型,不占存储空间,不能建立数组的引用
引用必须初始化,指针不必
引用初始化后不能改变,指针可以改变指向的对象
不存在指向空值的引用,存在指向空值的指针
传参时传引用与传指针效果相同
传引用,没有产生实参的副本,直接对实参操作
传指针,被调函数需要给形参分配空间,可读性差,需要传地址做实参,传引用更简单清晰
预处理、编译、汇编、链接
操作系统
用户告诉操作系统执行hello程序
操作系统到硬盘找到该程序
由编译程序将用户源程序编译成若干个目标模块
由链接程序将目标模块和相应的库函数链接成装入模块
操作系统分配内存,由装入程序将装入模块装入内存
为执行hello程序创建执行环境(创建新进程)
操作系统设置CPU上下文环境,并跳到程序开始处
程序的第一条指令执行
程序执行与printf对应的系统调用
操作系统分配设备
执行显示驱动程序
窗口系统将像素写入存储映像区
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。[这里指到叶子节点的路径]
模板特化、偏特化
内存池
C++ FAQ的更多相关文章
- Google软件构建工具Bazel FAQ
Google软件构建工具Bazel FAQ 本文是我的翻译,原文在这里.欢迎转载,转载请注名本文作者和原始链接 注:如果想了解Bazel的原理,可以看看我之前翻译的Google Blaze原理及使用方 ...
- 领域驱动设计常见问题FAQ
本文出处:http://www.cqrs.nu/Faq What is a domain? The field for which a system is built. Airport managem ...
- CQRS FAQ (翻译)
我从接触ddd到学习cqrs有6年多了, 其中也遇到了不少疑问, 也向很多的前辈牛人请教得到了很多宝贵的意见和建议. 偶尔的机会看到国外有个站点专门罗列了ddd, cqrs和事件溯源的常见问题. 其中 ...
- (译)关于async与await的FAQ
传送门:异步编程系列目录…… 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰富的API及性能提升,另外关键字”async” ...
- Async/Await FAQ
From time to time, I receive questions from developers which highlight either a need for more inform ...
- Unity3D热更新全书FAQ
只要有程序员朋友们问过两次的问题 就会收录在此FAQ中 1.C#Light对比LUA有什么好处 C#Light是静态类型脚本语言,语法同C#,Lua是动态类型脚本语言,这两种都有人喜欢. 我更喜欢静态 ...
- discuz /faq.php SQL Injection Vul
catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 . 通过获取管理员密码 . 对管理员密码进行破解.通过在cmd5.com ...
- Part 2: Oracle E-Business Suite on Cloud FAQ
Running Oracle E-Business Suite on Oracle Cloud is simple, but it doesn't take too much effort to co ...
- 转载:有关SQL server connection Keep Alive 的FAQ(3)
转载:http://blogs.msdn.com/b/apgcdsd/archive/2012/06/07/sql-server-connection-keep-alive-faq-3.aspx 这个 ...
- 转载:有关SQL server connection Keep Alive 的FAQ(2)
转: http://blogs.msdn.com/b/apgcdsd/archive/2012/05/18/sql-server-connection-keep-alive-faq-2.aspx 在下 ...
随机推荐
- flask from_object和from_pyfile的区别
flask from_object和from_pyfile的区别 from_object接受的是一个模块对象,需求导入 from_pyfile接受的是一个文件名的字符串,文件可以不是py文件也可以是 ...
- Linux 磁盘管理篇, 内存交换空间
swap是在系统内存不足的情况下,以硬盘暂时来储存内存中的一些数据来继续程序的执行 查看内存使用情况 free 格式化为swap格式 mkswap 启动sw ...
- Java并发之显式锁和隐式锁的区别
Java并发之显式锁和隐式锁的区别 在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别也就是说说Synchr ...
- web自动化测试中的PO模式(一)
1.PO模式的思想 原理: 将页面的元素定位和元素行为封装成一个page类 类的属性:元素的定位 类的行为:元素的操作 页面对象和测试用例分离 测试用例: 调用所需要页面对象中的行为,组成测试用例 测 ...
- 一个java 码手 的老牛 --- 涉及 一些不错的java 基础课程
http://www.zuidaima. com/user/1550463811307520/share/collect.htm
- 关于gpu版本的tensorflow+anaconda+jupyter的一些安装问题(持续更新)
关于anaconda安装,虽然清华镜像站资源很丰富,但是不知道是网络还是运气的问题,用这个路径安装的时候总是出现文件丢失.具体表现可能是anaconda prompt 找不到,conda命令无效等问题 ...
- Delphi学习手记——单引号和双引号的区别
单引号和双引号的区别 双引号表示其中字符可能包含变量,而单引号表示整个引号内的东西都当成字符串来处理. 也就是说:没有内设变量就用单引号'',有就用双引号"". 举例说明: $va ...
- stand up meeting 12-9
今天项目小组本已约好在今天下午四点半进行今天的daily scrum: 但是在四点半的时候,天赋和士杰同学均因组内项目会议延时,导致今天的daily scrum只能在晚上进行,但静雯同学因身体不舒服无 ...
- stand up meeting 12/17/2015
part 组员 今日工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 建立基本的pdf阅读器界面框架 4 开始实现界面框架 4 foxit PDF ...
- PHP函数:fwrite
fwrite() - 写入文件(可安全用于二进制文件) 说明: fwrite ( resource $handle , string $string [, int $length ] ) : int ...