Effective C++学习

导读

声明式

extern int x;

std::size_t numDigits(int num);

class Person;

template<typename T>
class GraphNode;

签名式

std::size_t (int); //这个函数获得一个int,返回一个size_t

定义式

int x;

std::size_t numDigits(int num)
{
    std::size_t digitSoFar = 1;
	while((num/10) != 0)  ++digitSoFar;
	return digitSoFar;
}

class Person{
public:
    Person();
    ~Person();
};

template<typename T>
class GraphNode{
public:
    GraphNode();
    ~GraphNode();
};

初始化(通常由构造函数执行,所谓默认构造函数就是可以被调用但不含实参, 要不无参要不都有默认参数)

class A{
public:
    A();
};
------------------------------------------------------------------------
class B{
public:
    explicit B(int x = 0; bool y = true); //explicit阻止隐式类型转换,但仍可被显式类型转换
};
------------------------------------------------------------------------
class C{
public:
    explicit C(int x); //这个就不是default构造函数
};
尽量使用explicit!!
------------------------------------------------------------------------
class D{
public:
    D();                     //默认构造函数
    D(const D& d)         //拷贝构造
    D& operator=(const D& d)//拷贝赋值构造
}

D d1;             //默认
D d2(d1);         //拷贝
d1 = d2;         //拷贝赋值
D d3 = d1;       //拷贝!!!! 区别就在于,新创建的d3是拷贝,已经存在的是拷贝赋值

class作为入参的时候,尽量不要值传递(本质上会用拷贝构造),用引用传递

条款01 视c++为一个语言联邦

何为联邦,即看作很多语言的组合

  1. C语言:以C为基础,但缺少模板、异常、重载
  2. object-oriented C++: 包括类(析构 构造) 封装 继承 多态 虚函数(动态绑定)
  3. Template c++: 泛型编程
  4. STL:包含容器 算法 迭代器 函数对象
  • c一般用值传递,c++引用传递

条款02 尽量以const enum inline 替换#define

  1. 意思是:尽量用编译器,而不是预处理器
  • 原因1:用#define A 1.2 的话,A可能不会进入记号表,后面报错不会有A,只会有1.2 并不知道具体是哪
  • 原因2:用#define会导致每个地方都展开,生成很多oject code(类机器语言) 但如果用const double就不会
  • 使用const的时候,一定要用指向的常量的指针
  • define的变量不具有作用域,必要时需要在class privite里定义
const char* str = "hello"; //指针str指向常量,不能通过str修改hello的值,但是可以改变指向, 不能改char*,即*str = "abc"报错
char* const str = "hello"; //指针指向一块固定的地方,但是可以改这块地址的值,,不能改str,即str = &p2 报错
const char* const str = "hello" //不能通过指针str修改hello的值,也不能改变指向

//所以 我们一般用第三个来代替#define,同时用string代替char*
const std::string str("hello")

//静态成员变量一般不允许类内初始化,可能会导致一个很尴尬的情况,如下
class Person{
privete:
	//static const int age;
	enum {age = 5};
	int score[age];         //age不能类内初始化,所以这个地方会报错,解决方案是用enum
}
enum变量无法取他的地址,这个和define很像


//宏展开函数会带来意想不到的后果
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))  //用 a b 较大的一个调用f(x)函数

//因为直接宏替换,会导致第一次选择b(a+1),第二次选择a (a+2)
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);
CALL_WITH_MAX(++a, b+10);

//使用模板+inline可以解决此问题
template <typename T>
inline void CallWithMax(const T&a,const T&b)
{
	f(a > b ? a : b)
}

总结 : 单纯变量用const/enum替换#define,形似函数的宏,用inline替换

条款03 尽量使用const

  • 迭代器本身可以++,迭代器前的const 相当于T* const xxx
std::vector<int> vec;
const std::vector<int>::iterator it = vec.begin(); //相当于T* const
*it = 10; //OK
++it; //NOK

std::vector<int>::const_iterator it = vec.begin(); //相当于const T*
*it = 10; //NOK
++it; //OK

  • 类实例化为const 就只能调用const成员函数
  • 普通类的实例,是非const都能调用
  • const成员函数只能修改mutable成员变量(如果const成员函数返回成员的一个指针,还是可以通过指针修改的,这样不算)
+ 正常优先调用重载的非const函数,非要调用const要这么做 (也有可能避免重载函数重复):
class Person {
    public:
      const char& operator[](std::size_t pos) const{
          return text[pos];
      }

      char& operator[](std::size_t pos) {
          //return text[pos];
          return const_cast<char&>(static_cast<const Person&>(*this)[pos];
          // 含义是 把*this(this是指向当前对象指针 *解引用 表示当前对象) 强转为const对象,强制调用const的成员函数,然后把结果的const强制去掉(const_cast)
      }
      privite:
        string text;
}

条款04 使用前初始化

  • 对于class成员变量,推荐使用成员初值列,以声明顺序初始化,而非初值列的顺序!
Person::Person(const std::string &name, const std::vector<int> &phones) : name(name), phones(phones), age(0) {} // name phones ages都是成员变量,三者都是拷贝构造
Person::Person() : name(), phones(), age(0) {} 默认构造

  • A全局static变量的初始化依赖另一个编译单元的全局static变量B,但是B还没初始化,问题出现了(全局static变量只是说具有从头到尾生命周期的变量)
// ✅ 有问题的变量(跨编译单元依赖)
MyClass objA(1);              // 全局变量(外部链接)
static MyClass objB(2);       // 全局变量(内部链接)
namespace { MyClass objC(3); } // 匿名命名空间中的变量

  • 解决方案是不要直接ObjA objA 初始化,而是另起一个函数
ObjA &getObj()
{
    static ObjA obj;
    return obj;
}

外部本来可能是extern ObjA obj ,现在改为调用getObj函数

条款05 了解c++默认编写调用的函数

  • // 以下四个都是需要时自动创建(public且inline)的,构造、析构、拷贝构造、拷贝赋值(定义了则不会自动创建)
  • 拷贝构造和拷贝复制只处理源对象的非静态成员变量,对于内置类型复制每个bit,对于其他如string直接调用它的构造函数
  • 如果class成员里是引用(不允许改变引用指向)或者class等,会编译失败,而不是编译器帮你创建拷贝构造拷贝赋值
class Empty {
public:
    Empty() {}
    Empty(const Empty& empty){}
    ~Empty(){}
    Empty()& operator=(const Empty& empty){}
}

条款06 不想编译器自动生成的函数需手动删除

  • 方式1:声明为private且不写定义,正常编译就报错,要是想通过friend等方式绕过,链接会报错
  • 方式2:父类的对应函数声明为private, 继承这个父类,编译就全爆了,包含friend的绕过方法
  • 方式3: = delete;

条款07 为多子父类声明虚析构

  • 父类的析构非虚,只析构自己不管子类,因此有子类的必须用虚析构
  • 类内有其他虚函数的,必须用虚析构
  • 此外情况不要用虚析构,增加开销(多一个虚函数表)
  • final关键字禁止派生子类,stl基本都不能派生,有类似的问题 class Person final {}
posted @ 2024-09-26 15:47  __Zed  阅读(80)  评论(0)    收藏  举报