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 在下 ...
随机推荐
- SI24R2F新一代2.4G超低功耗单发射有源RFID芯片 SI24R2E升级版智能充电安全管理方案首选
目前全国有很多电动车因在充电时电池温度过高,而导致爆炸引起火灾的情况.作为国内RFID行业的推动者,动能世纪联合中科微向IOT应用领域推出新款大功率2.4G射频芯片,并针对电动车防盗.电动车充电桩市场 ...
- web自动化浏览器chrome和驱动chromedriver
1.web自动化下载浏览器和对应的浏览器驱动,以谷歌浏览器为例 电脑上安装谷歌浏览器,查看谷歌浏览器的版本,输入chrome://settings/help 2.chromedriver国内镜像地址h ...
- mpvue中使用flyjs全局拦截
mpvue全局属性设置,在我之前的文章中有介绍,今天想记录的就是怎么和Fly.js结合使用来实现全局拦截功能: 首先我们要安装好Flyio,在mpvue项目中我们用npm下载安装: npm insta ...
- Vulnhub DC-2靶机渗透
信息搜集 nmap扫描端口 nmap -sV 192.168.146.140 -p1-10000 开了80端口,那就直接访问一下把.(7744端口是ssh端口,之后会用到) 输入ip,发现url处变成 ...
- 15分钟从零开始搭建支持10w+用户的生产环境(一)
前言 这是一个基于中小型企业或团队的架构设计. 不考虑大厂.有充分的理由相信,大厂有绝对的实力来搭建一个相当复杂的环境. 中小型企业或团队是个什么样子? 开发团队人员配置不全,部分人员身兼开发过程上下 ...
- python3(二十二) oop
""" 面向对象编程 """ __author__ = 'shaozhiqi' # 面向对象的程序设计把计算机程序视为一组对象的集合,而每个 ...
- coding++:都说新的Arraylist 扩容是(1.5倍+1) 看了1.8的源代码发现不是这么回事
都说新的Arraylist 扩容是(1.5倍+1) 看了1.8的源代码发现不是这么回事 就用下面这段代码在jdk的三个版本运行看了下效果: import java.lang.reflect.Fiel ...
- 概率专题_概率/ 数学_基础题_ABEI
上周三讲了概率和概率dp.如果没有涉及其他综合算法,概率这种题主要是思维,先把这部分的东西写完 给个题目链接:https://vjudge.net/contest/365300#problem Hea ...
- 浅谈Vector
浅谈Vector 在之前的文章中,我们已经说过线程不安全的ArrayList和LinkedList,今天我们来讲讲一个线程安全的列表容器,他就是Vector,他的底层和ArrayList一样使用数组来 ...
- Delphi Unicode转中文
function UniCode2GB(S : String):String;Var I: Integer;beginI := Length(S);while I >=4 do begintry ...