声明和定义
- 变量定义:为变量分配空间,还可以为变量指定初始值。
- 变量声明:向程序表明变量的类型和名字,但不分配空间。可以通过 (extern) 关键字来声明而不定义,(extern) 告诉编译器变量在别的地方定义了。
- 定义也是声明,声明不是定义。例如:
- extern int i 声明且不定义 i 变量,int i 声明且定义了 i 变量。
- 声明有初始值时,为当成定义
- extern int i=10 此时看成定义了 i 变量。
- 函数的声明和定义,区别在于是否带有花括号。
面向过程和面向对象
面向过程就是分析出解决问题所需要的步骤,然后用函数把步骤一步一步的实现。优点在于性能高。
面向对象就是构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个对象在整个解决问题的步骤中的行为。优点在于易维护、易复用、易扩展,使系统更加灵活。
堆区和自由存储区
基本上所有的 (C)++ 编译器默认使用堆来实现自由存储,也就是缺省的 (new/delete) 是通过 (malloc/free) 方式来实现的,这时候可以说他从堆上分配内存,也可以说他从自由存储区分配内存。但程序员可以通过重载操作符,改用其他内存实现自由存储。
堆区是操作系统维护的一块内存,而自由存储区是 (C)++ 中用于 (new/delete) 动态分配和释放对象的 抽象概念。
堆区和栈区
堆区 | 栈区 | |
---|---|---|
管理方式 | 由程序员控制 | 系统主动分配 |
空间大小 | 空间很大,可以达到(4G) | 默认(1M) |
碎片问题 | 频繁的 (malloc/free) 会造成大量碎片 | 不会存在这个问题 |
生长方向 | 向着内存地址增加的方向 | 向着内存地址减小的方向 |
分配效率 | 速度比较慢 | 速度比较快 |
#ifndef 和 #endif 的作用
在大的软件工程中,可能多个文件包含同一个头文件,当这些文件链接成可执行文件的时候,就会造成大量的 "重定义" 错误,可以通过 #ifndef 来避免这个错误。
#ifndef _A_H
#define _A_H
#endif
这样就可以保证 (a.h) 被定义一次。
(explicit) 关键字
类的构造函数存在隐式转换,如果想要避免这个功能,就可以通过 (explicit) 关键字来将构造函数声明成显式的。
菱形继承
class A {
public:
void fun() {
cout << "!!!" << endl;
}
};
class B : public A{
};
class C : public A{
};
class D : public B, public C {
};
int main() {
// freopen("in", "r", stdin);
D d;
d.fun();;
return 0;
}
像上面的继承关系,当继承关系形成菱形时,(D) 中保存了两份 (A),在调用 (d.fun()) 时就会出现调用不明确的问题。
这种情况由两种解决方法:
- 使用域限定访问的函数。也就是将 (d.fun) 修改成 (d.B::fun) 或者 (d.C::fun)。
- 第二种方法就是使用虚继承。虚继承解决了从不同途径继承来的同名数据成员在内存中有不同的拷贝造成数据不一致的问题,将共同基类设置成虚基类,这时从不同路径继承过来的同名数据成员在内存中就只有一个拷贝。操作方法就是在 (B) 和 (C) 的继承处加上 (virtual) 修饰。
虚继承底层实现一般通过虚基类指针和虚基类表实现。每个虚继承的子类都有一个虚基类指针和一个虚基类表,虚表中记录了虚基类与本类的偏移地址。通过偏移地址,这样就找到了虚基类成员,节省了存储空间。
(weak\_ptr) 指向的对象被销毁
首先 (weak\_ptr) 是一种不控制对象生存周期的智能指针,它指向一个 (shared\_ptr) 管理的对象。一旦最后一个指向对象的 (shared\_ptr) 被销毁,那么无论是否有 (weak\_ptr) 指向该对象,都会释放资源。
(weak\_ptr) 不能直接指向对象,需要先调用 (lock),而 (lock) 会先判断该对象是否还存在。
如果存在 (lock) 就会返回一个指向该对象的 (shared\_ptr),并且该对象的 (shared\_ptr) 的引用计数加一。
如果在 (weak\_ptr) 获得过程中,原本的所有 (shared\_ptr) 被销毁,那么该对象的生命周期会延长到这个临时 (shared\_ptr) 销毁为止。