一、構造函數和析構函數
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;
};
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/257660.html
微信掃一掃
支付寶掃一掃