C++ 是一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。
C
Object-Oriented C++:C with classes
Template C++
STL
#define 只是单纯的文本替换,可能会因为操作符优先级等因素出错。
(1)对于常量,用 const 代替 #define,例如
const int MAX = 20;
而不是
#define MAX 20
(2)对于形似参数的宏,最好用 inline 函数代替 #define,如
#define CALL_WITH_MAX(a, b) f((a)>(b)) ? (a) : (b)
一定要加括号,否则可能因运算符优先级而出错(实际上加了括号也可能会出错)。所以改用更为方便的内联函数
template <typename T>
inline void callWithMax(const T& a, const T& b)
{
f(a > b ? a : b);
}
关于内联函数:内联函数在调用时,是将调用表达式用内联函数体来替换。且只适用于 1~5 行的小程序。
const 在星号左边,则被指物是常量;在星号右边,则指针本身是常量;出现在星号两边,则被指物和指针都是常量。
const int * ptr // ptr 指向的是常量
int const * ptr // 和上面等价
int* const ptr // ptr 本身是常量,也就是他的指向不可改变
const int* const ptr // 都是常量,不可改变
构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。
建议
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list& phones)
: theName(neme), // 列表初始化
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{ }
而不是
ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list& phones)
{
theName = neme; // 这是赋值而不是初始化
theAddress = address;
thePhones = phones;
numTimesConsulted = 0;
}
如果自己没有定义,编译器就会自动为类创建默认构造函数、复制构造函数、copy assignment 操作符以及析构函数;自己定义之后,编译器就不再提供该函数了。
注意,是当被调用时,他们才会被创建出来。
例如,创建一个 Empty 类,
class Empty {};
编译器会提供默认函数,相当于
class Empty
{
public:
Empty() {...} // 默认构造函数
// 析构函数
Empty(const Empty& rhs) {...} // 拷贝构造函数
~Empty() {...} // 析构函数
Empty& operator=(const Empty& rhs) {...} // copy assignment 操作符
};
当调用时,就会被创建出来
Empty e1; // 默认构造函数
Empty e2(e1); // 拷贝构造函数
e2 = e1; // copy assignment 操作符
还有一点需要注意的是,当我们自己定义了一个有参构造函数时,例如
Empty::Empty(int a) {...} // 有参构造函数
编译器就不再提供默认构造函数,这时语句
Empty e1;
就会报错。所以当类中有自定义构造函数时,还需要手动添加一个默认构造函数,以防初始化对象失败。
有时我们希望由类初始化的对象是唯一的,也就是禁止拷贝构造和 copy assignment 操作符,那么就可以将拷贝构造函数和 copy assignment 声明为 private,这样在外部就无法调用了。
例如,初始化一个 HomeForSale 对象,我们希望他是独一无二的,即
HomeForSale h1;
HomeForSale h2; // 允许初始化
HomeForSale h3(h2); // 不允许拷贝构造
h2 = h1; // 不允许 copy assignment
这时,可如下声明
class HomeForSale
{
public:
...
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
// 声明为私有
};
还有一种情况,我们不希望直接创建基类 A 的对象,而是仅允许创建其子类 B 的对象,这时,可将类 A 的构造函数和析构函数声明为 private 或 protected,例如
class A // 基类
{
protected:
A(){} // 默认构造函数
~A(){} // 析构函数
public:
...
};
calss B : public A // 子类
{
public:
B(){} // 构造函数
};
A a; // error,外部无法调用构造函数
B b; // ok
关于虚函数和多态:
https://www.runoob.com/w3cnote/cpp-virtual-functions.html
http://c.biancheng.net/view/264.html
如果一个类拥有至少一个虚函数,那么这个类也应该要有一个虚析构函数。例如有一个基类 A 和派生类 B,定义一个 A 类型的指针,指向 B 对象
A* ptr = new(B);
delete ptr;
如果 A 中的析构函数是非虚函数,当执行 delete ptr
时,销毁的仅是 B 中继承自 A 的部分,也就是说,B 对象派生部分未被销毁而发生内存泄露。当基类 A 中析构函数为虚函数时,delete ptr
调用 B 自身的析构函数,销毁的就是 B 对象。
反过来,如果一个类不想作为基类,那么不要为其声明任何成员函数做 virtual 声明。
https://www.cnblogs.com/pandamohist/p/13889691.html
https://zhuanlan.zhihu.com/p/143167853
class Widget {
public:
Widget& operator=(const Widget& rhs) {
...
return *this;
}
Widget& operator+=(const Widget& rhs) {
...
return *this;
}
}
再类中重载运算符时,建议返回 reference to *this
。
自己写拷贝构造函数和 copy assignment 操作符时,一定要确保复制“对象内的所有成员变量”。
尽量以 pass-by-reference-to-const 替换 pass-by-value,省去了创建副本的时间,没有任何构造函数和析构函数被调用,更加高效。
对于内置类型以及 STL 的迭代器和函数对象,使用 pass-by-value 效率更高。
也就是说,如果是自己创建的比较复杂的数据类型,尽量用pass-by-reference-to-const;而自带的类型,建议pass-by-value。
将成员变量声明 private,在 public 中定义获取私有变量值的函数,从而避免外界随意修改私有变量。
public 继承意味着 is-a ,也就说,适用于父类身上的每一件事情也适用于子类,二者是包含与被包含的关系。