C++笔记(面向对象高级编程--侯捷)
inline内联函数
在C/C++中,有些函数会被频繁的调用,会有频繁的函数入栈、出栈操作,这样就造成一定程度上时间以及空间的消耗。于是在C++中便提出了内联函数的概念。
内联函数的特征:
- 在编译时,编译器使用函数的定义替换掉函数的调用语句,这种替代行为发生在编译阶段而非程序运行阶段
- 在类声明中定义的函数, 除了虚函数的其他函数都会自动隐式的成为内联函数
- 关键字
inline
必须与函数定义体放在一起才能使函数成为内联, 仅将inline放在函数声明前不起任何作用
优点:
- 省去函数调用时的参数压栈, 栈帧开辟, 结构返回等步骤, 从而提高了程序的运行速度
- 内联函数优于宏,宏不能按值传递,并且内联函数可以访问类的成员变量
- 内联函数在运行时可以调试, 而宏定义则不行(因为宏定义是被预定义处理的, 所以不会有人黑的编译符号和调试信息, 调试的时候基本只能用肉眼去看)
缺点:
- 代码膨胀,内联函数增大了可执行程序的体积
- C++内联函数的展开是中编译阶段,这就意味着如果你的内联函数发生了改动,那么就需要重新编译代码
- 是否内联是程序员不可控的,
inline
只是对编译器的建议,最终有编译器决定
构造函数(初始化、赋值)
构造函数可以分为两步:
- 初始化
- 赋值
对比以下两种方式的构造函数,方式一在直接通过参数初始化变量;而方式二先初始化,然后再对变量进行赋值。显然效率就有差异。
// 方式一:初始化
Complex(double r = 0, double i = 0) : re(r), im(i) { }
// 方式二:赋值
Complex(double r = 0, double i = 0) {
re = r;
im = i;
}
const
常量
关键字 const
限定常量只读。仅在声明时进行初始化,若声明变量时没有提供值,则该常量的值将是不确定的,且无法修改。
const
优于 #define
:
- const 能够明确指定类型
- 可以使用C++的作用域规则将定义限定在特定的函数或文件中
- 可以将 const 用于更复杂的类型
const函数
通常我的理解是用 const 修饰函数也就是标识函数不会修改数据。此时有了新的理解。先看简略代码如下:
// 类的部分定义
class Complex{
public:
Complex(double r = 0, double i = 0) : re(r), im(i) { }
double real() /* const */ { return re; }
double imag() /* const */ { return im; }
private:
double re, im;
};
// 类对象的使用
int main(){
const Complex c(1,2); // 常量对象
cout << c.real() << " " << c.imag(); // 函数调用可能修改值,矛盾!编译器
return 0;
}
我们以上面代码来分析为什么要用 const ,我们定义的常量复数类对象打印输出复数实部和虚部,假如两个函数没有 const 修饰,意思是函数可能改数据,但是对象是常量,不允许改数据,这是 矛盾的 ,那么编译器表示不行。
const object(data members 不可改变) | non-const object(data members 可改变) | |
---|---|---|
const member functions | √ | √ |
non-const member functions | × | √ |
当成员函数的 const
和 non-const
版本同时存在,const object 只会(只能)调用 const 版本,non-const object 只会(只能)调用 non-const 版本。
friend友元
友元函数可直接访问私有成员。
同一个 lass
的各个 objects
互为友元。 (如:拷贝构造函数)
友元关系不可传递、不可继承。(你是我的朋友,但你的朋友不一定是我的朋友)
拷贝构造、拷贝赋值
class 带有 pointer members
必须有拷贝构造(深拷贝)和拷贝赋值。
inline
String::String(const char* cstr) { // 拷贝构造
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String& String::operator=(const String& str) { // 拷贝赋值
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
构造函数、析构函数
new
:先分配内存 memory
,再调用构造 constructor
。
String* ps = new String("Hello");
编译器转化为:
String* ps;
void* mem = operator new(sizeof(String)); // 分配内存 内部调用 malloc
ps = static_cast<String*>(mem); // 转型
ps->String::String("Hello"); //构造函数
delete
:先再析构 destructor
,再释放内存 memory
。
delete ps;
编译器转化为:
String::~String(ps); // 析构函数
operator delete(ps); // 释放内存 内部调用 free(ps)
动态分配所得内存块(in VC)
创建Complex对象、String对象,编译器(VC)给两对象分配内存如下:
![动态分配所得内存块.png](https://i.loli.net/2020/09/16/oXHKxDMsbdeuZ7w.png)
-
绿色部分是对象实际占有的空间
-
红色部分是cookie,编译器在对象内存的头尾插入cookie,cookie值表示内存块大小以及状态,如图中:
cookie = 0x00000041
内存块大小 64 字节,对应十六进制数 0x40
内存块是已分配的(给出去了)cookie最后一位为 1 标识,由此的到cookie值 0x00000041
-
灰色部分是debug模式下编译器分配空间时需要插入的字节,VC下为 4*8+4 字节
-
青色部分为字节对齐(VC为16字节对齐)
对于数组array,内存块如下:
![动态分配array所得内存块.png](https://i.loli.net/2020/09/16/D2OunVsfGLHo4tN.png)
array new 一定要搭配 array delete,下图给出内存泄露位置:
![array-new一定要搭配array-delete.png](https://i.loli.net/2020/09/16/5ib9a1IGFDvWm2M.png)
复合、委托、继承
Composition
(复合):
class queue {
deque c; // 复合 (包含对象)
}
- 表示 has-a 关系。
- 构造函数由内向外
- 析构函数由外向内
Delegation
(委托):
个人理解有点文件存储node结点的味道。引用计数。写时复制,读时共享。
class String {
StringRep* rep; // 指针 指向实现功能的类
}
Inheritance
(继承):
- 表示 is-a 关系(是一种)
- 构造函数由内而外(父类---子类)
- 析构函数由外而内(子类---父类)、父类析构必须为
virtual
复合(Composition)+继承(Inheritance)
析构函数顺序与构造函数恰好相反。
-
情况一
构造函数 :Base ---> Composition ---> Derived
-
情况二(直接推导即知)
构造函数 :Composition ---> Base ---> Derived
虚函数
non-virtual
函数:非虚函数,不希望派生类重新定义(override)它;
vitual
函数:虚函数,希望派生类重新定义,且已有默认定义;
pure virtual
函数:纯虚函数,希望派生类一定要重新定义它,且,没有默认的定义。
智能指针
仿函数
仿函数(functors)另名函数对象(function objects)。
函数有函数名称,用一个 ()
作用上去,任何一个东西如果可以接受 ()
操作符,我们就称它 function-like 。每个仿函数都是某个类重载 ()
运算符,然后变成了“仿函数”,实质还是一个类,但看起来具有函数的属性。
对 ()
的重载,都继承了 unary_function
或 binary_function
。
template <class _Arg, class _Result>
struct unary_function { // 一元函数
typedef _Arg argument_type;
typedef _Result result_type;
};
template <class _Arg1, class _Arg2, class _Result>
struct binary_function { // 二元函数
typedef _Arg1 first_argument_type;
typedef _Arg2 second_argument_type;
typedef _Result result_type;
};
模板泛化、特化
泛化:
template <class Key>
struct hash { };
特化:模板特化指的是模板中指定特定的数据类型,这和泛化是不同的
template<>
struct hash<int> {
size_t operator()(int x) const { retrun x; }
};
模板特化也有程度之分,可以部分类型指定,称之为偏特化。
C++11
数量不定的模板参数
...
就是所谓的 pack(包) ,例如:
void print() { }
template <class T, class... Types>
void print(const T& firstArg, const Types&... args) {
cout << firstArg << endl;
print(args...);
}
auto
auto ite = find(c.begin(), c.end(), target);
ranged-base for
for (decl : coll){
statement
}
虚指针、虚表
只要类里有虚函数,类的对象就有一个指针(vptr
),指向一个虚函数表(虚表,vtbl
)。基类有虚函数,派生类必然有虚函数。
![虚函数-虚指针-虚表.jpg](https://i.loli.net/2020/09/17/ImqXaP8hBDcLvWu.jpg)