空类

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调用虚函数ff作用的可能是基类对象,也可能是派生类对象,这就是多态(同样消息作用于不同类型对象产生不同的行为)的一种方式,即动态多态。

正因为编译器无法确定使用哪个虚函数,所以所有的虚函数必须定义,否则编译器会报错。

构造函数不能是虚函数,因为构造对象时必须明确知道其类型。如果是虚函数,调用时只需要提供接口,编译器无法知道你想构造继承树的哪个类型。

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 = &num; // p将一直指向num
  • 指向常量的指针(pointer to const):指针指向的对象不可变。
const double pi = 3.14;
double* p = &pi; // 错误,p是一个普通指针
const double* p = &pi; // 正确
*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的更多相关文章

  1. Google软件构建工具Bazel FAQ

    Google软件构建工具Bazel FAQ 本文是我的翻译,原文在这里.欢迎转载,转载请注名本文作者和原始链接 注:如果想了解Bazel的原理,可以看看我之前翻译的Google Blaze原理及使用方 ...

  2. 领域驱动设计常见问题FAQ

    本文出处:http://www.cqrs.nu/Faq What is a domain? The field for which a system is built. Airport managem ...

  3. CQRS FAQ (翻译)

    我从接触ddd到学习cqrs有6年多了, 其中也遇到了不少疑问, 也向很多的前辈牛人请教得到了很多宝贵的意见和建议. 偶尔的机会看到国外有个站点专门罗列了ddd, cqrs和事件溯源的常见问题. 其中 ...

  4. (译)关于async与await的FAQ

    传送门:异步编程系列目录…… 环境:VS2012(尽管System.Threading.Tasks在.net4.0就引入,在.net4.5中为其增加了更丰富的API及性能提升,另外关键字”async” ...

  5. Async/Await FAQ

    From time to time, I receive questions from developers which highlight either a need for more inform ...

  6. Unity3D热更新全书FAQ

    只要有程序员朋友们问过两次的问题 就会收录在此FAQ中 1.C#Light对比LUA有什么好处 C#Light是静态类型脚本语言,语法同C#,Lua是动态类型脚本语言,这两种都有人喜欢. 我更喜欢静态 ...

  7. discuz /faq.php SQL Injection Vul

    catalog . 漏洞描述 . 漏洞触发条件 . 漏洞影响范围 . 漏洞代码分析 . 防御方法 . 攻防思考 1. 漏洞描述 . 通过获取管理员密码 . 对管理员密码进行破解.通过在cmd5.com ...

  8. 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 ...

  9. 转载:有关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 这个 ...

  10. 转载:有关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 在下 ...

随机推荐

  1. 微信小程序placeholder设置自定义颜色

    原地址链接:https://blog.csdn.net/august_leo/article/details/80877382 这是微信小程序input组件的官方文档描述,下图红框里的placehol ...

  2. 这个案例写出来,还怕跟面试官扯不明白 OAuth2 登录流程?

    昨天和小伙伴们介绍了 OAuth2 的基本概念,在讲解 Spring Cloud Security OAuth2 之前,我还是先来通过实际代码来和小伙伴们把 OAuth2 中的各个授权模式走一遍,今天 ...

  3. 抓包工具fiddler安装和配置

    常见的抓包工具:fiddler.wireshark,本文以安装fiddler为例: 在官网上https://www.telerik.com/fiddler下载,安装后打开fiddler. 选择好自己的 ...

  4. hadoop(五)scp命令copy文件和配置(完全分布式准备二)|7

    机器的克隆参考centos7克隆ip|机器名|映射关系|别名配置(hadoop完全分布式准备一) 那么问题来了,如果我们有30台机器,之间可以互相访问,那我们如何快速安装配置环境如jdk hadoop ...

  5. 1、jmeter语言设置、版本颜色

  6. break与continue对比

    - break 用来终止循环 - continue 用来跳出当前循环,继续下次循环 // 求1到100之间所有不能被3整除的整数的第一个大于2000的和 var sum = 0; for(var i= ...

  7. 【Java】WrapperClass 包装类

    什么是包装类? 写写我的想法 就是对于对象和基本类型的无法匹配和强转,基本类型在面向对象的实例类型中,反而成了个特殊的数据类型的存在 在一些特定的情况,我们希望通过对象的方式去处理数据,但是基本类型的 ...

  8. 10个步骤教你如何安装Anaconda安装,Python数据分析入门必看

    前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:小白 PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行 ...

  9. 今天我们来讨论一下CSS3属性中的transition属性;

    transition属性是CSS3属性:顾名思义英文为过渡的意思:主要有四个值与其一一对应:分别是property(CSS属性名称),duration过渡的时长,timimg-function转速曲线 ...

  10. python selenium模块 css定位

    selenium是python的非标准库,使用时需要下载安装 安装命令  pip install selenium selenium是python的自动化测试模块,可以模拟浏览器的行为 所以在使用之前 ...