一、构造函数和析构函数
1、在构造函数和析构函数中应该使用static_cast而不是基于C的强制类型转换。
class Base{
public:
Base(int i = 0) : m_i(i){} //构造函数使用static_cast
virtual ~Base() = default; //析构函数使用static_cast
private:
int m_i;
};
class Derived : public Base {
public:
Derived(double d = 0.0)
: Base(static_cast
(d)), m_d(d){} //使用静态转换,避免可能出现的错误
private:
double m_d;
};
2、将析构函数声明为虚函数可以确保基类的析构函数被正确地调用,因为这样可以通过一个指向基类对象的指针来调用派生类的析构函数。这个对于继承来说是非常重要的。
class Base{
public:
Base(){}
virtual ~Base(){} //将析构函数声明为虚函数
};
class Derived : public Base {
public:
Derived(){}
~Derived() override{} //使用override重载析构函数
};
3、在构造函数中应该使用初始化列表来初始化变量,否则会先调用默认构造函数,再进行一次赋值操作,影响效率。
class Foo{
public:
Foo(){}
Foo(int i, double d) : m_i(i), m_d(d){} //使用初始化列表初始化变量
private:
int m_i;
double m_d;
};
二、拷贝构造函数和赋值函数
1、如果没有必要,不要使用拷贝构造函数和赋值函数。
class Foo{
public:
Foo(){}
Foo(const Foo& foo) = delete; //删除拷贝构造函数
Foo& operator=(const Foo& foo) = delete; //删除赋值函数
};
2、如果必须要用到拷贝构造函数和赋值函数,那么一定要正确地实现它们,特别是当实现类有指针成员时。
class String{
public:
String(const char* str = "") //构造函数
:m_str(new char[strlen(str) + 1]) //分配内存
{
strcpy(m_str, str);
}
String(const String& s) //拷贝构造函数
:m_str(new char[strlen(s.m_str) + 1]) //分配内存
{
strcpy(m_str, s.m_str);
}
String& operator=(const String& s) //赋值函数
{
if(this == &s) return *this; //如果自我赋值,直接返回
delete[] m_str;
m_str = new char[strlen(s.m_str) + 1]; //分配内存
strcpy(m_str, s.m_str);
return *this;
}
~String(){delete[] m_str;} //析构函数
private:
char* m_str;
};
3、拷贝构造函数和赋值函数应该以const引用传参。
class Foo{
public:
Foo(){}
Foo(const Foo& foo){} //拷贝构造函数以const引用传参
Foo& operator=(const Foo& foo){return *this;} //赋值函数以const引用传参
};
三、运算符重载
1、运算符重载应该在实现非成员函数时以友元函数的形式来实现,因为这样可以访问私有成员变量。
class Foo{
public:
Foo(){}
friend Foo operator+(const Foo& lhs, const Foo& rhs){return Foo();} //重载加号运算符
};
2、重载<<运算符时应该返回一个流对象的引用,这样可以通过链式调用的方式输出多个对象。
class Foo{
public:
Foo(){}
friend std::ostream& operator<<(std::ostream& os, const Foo& foo) //以流对象的引用作为返回值
{
os << "output something...";
return os;
}
};
3、运算符重载中需要注意处理自我赋值的情况。
class Foo{
public:
Foo(){}
Foo& operator+=(const Foo& foo) //重载+=运算符
{
//处理自我赋值
return *this;
}
};
四、继承与多态
1、在公有继承时,一定不要试图修改基类中的变量,因为这会对所有派生类都造成影响。
class Base{
public:
Base(){}
protected:
int m_i;
};
class Derived : public Base {
public:
Derived(){}
void change(int i){m_i = i;} //不要试图修改基类中的变量
};
2、将继承类中自己的数据和行为都封装起来,不要让派生类直接访问基类的成员变量和函数。这样可以确保继承是基于接口的而不是基于实现的。
class Shape{
public:
Shape(){}
virtual ~Shape(){}
virtual double area() const = 0; //纯虚函数
};
class Circle : public Shape {
public:
Circle(){}
double area() const override //实现基类中的纯虚函数
{
return PI * m_r * m_r;
}
private:
double m_r;
};
class Square : public Shape {
public:
Square(){}
double area() const override //实现基类中的纯虚函数
{
return m_a * m_a;
}
private:
double m_a;
};
3、使用虚函数时一定要记得加上virtual关键字。
class Base{
public:
Base(){}
virtual ~Base(){}
virtual void func(){std::cout << "Base::func() called." << std::endl;} //使用virtual关键字
};
class Derived : public Base {
public:
Derived(){}
void func() override{std::cout << "Derived::func() called." << std::endl;} //使用override关键字
};
五、模板和STL
1、使用typename而不是class来声明模板类型。
template <typename T> //使用typename而不是class
class Foo{
public:
Foo(){}
};
Foo<int> foo; //使用时指定类型
2、在STL中使用迭代器时,如果不需要对迭代器进行修改,应该使用const_iterator而不是iterator。
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
for(std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it) //使用const_iterator
{
std::cout << *it << std::endl;
}
3、在实现自己的容器类时,应该遵循STL的接口规范,比如提供begin、end、size等函数。
template <typename T>
class MyVector{
public:
MyVector() : m_size(0){}
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() {return &m_data[0];} //提供begin函数
iterator end() {return &m_data[m_size];} //提供end函数
const_iterator begin() const {return &m_data[0];}
const_iterator end() const {return &m_data[m_size];}
size_t size() const {return m_size;} //提供size函数
private:
T m_data[100];
size_t m_size;
};