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 在下 ...
随机推荐
- tornado的ORM
tornado的ORM 安装sqlalchemy和pymysql pip install sqlalchemy pip install pymysql 连接数据库 from sqlalchemy im ...
- 【C#】写一个支持多人聊天的TCP程序
碎碎念 先谈谈我们要实现的效果:客户端可以选择要聊天的对象,或者直接广播消息(类似QQ的私聊和群消息) 那么,该如何实现呢? 首先明确的是,要分客户端和服务器端两个部分(废话) 客户端:选择要发送的对 ...
- python3(十六) sorted
# sorted()函数list进行排序: L = sorted([36, 5, -12, 9, -21]) print(L) # [-21, -12, 5, 9, 36] # 可以看到默认是按照升序 ...
- Idea离线安装plugins插件 如Lombok
由于公司不允许使用外网,之前用的idea 15 安装了一次.但是idea15的提示不够友好,今天升级idea2017.3.2,同样又需要安装,那就写个教程吧. 网上其他的安装教程不通用,也是针对不同i ...
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(五)之Controlling Execution
In Java, the keywords include if-else,while,do-while,for,return,break, and a selection statement cal ...
- 打印图片的属性和实现另存图片功能以及使用numpy
上一篇我们已经学了如何读取图片的功能了以及和opencv的环境搭建了,今天接着来学习,哈哈哈,今天刚好五一,也没闲着,继续学习. 1. 首先我们来实现打印出图片的一些属性功能, 先来看一段代码: im ...
- DPK
一.概念 dpk文件是Delphi的包文件,有dpk文件的组件安装比较方便.一般来说,支持不同版本Delphi的组件会有不同的dpk文件,一般以7结尾的dpk文件是支持Delphi 7的.如果没有支持 ...
- day18作业
作业: # 1.编写课上讲解的有参装饰器准备明天默写 def auth(file_type): def outer(func): def inter(*args,**kwargs): if file_ ...
- I NEED A OFFER! HDU - 1203
概率+0 1背包 要算成功的最大概率,那就是失败的最小概率,所以01背包直接让失败的概率最小就行了. 注意: 概率与概率之间是要相乘的,不是相加. #include<bits/stdc++.h& ...
- K - Two Contests
题目连接:https://atcoder.jp/contests/agc040/tasks/agc040_b 大佬题解:https://blog.csdn.net/duanghaha/article/ ...