effective cpp小结条款1~20

条款2

#define具有全局性,出来的是宏,它是预处理的东西,预处理后的编译阶段已经不存在,所以不可能获取宏的地址。
enum,非全局,内元素也不能取地址(右值),因为常量存放在内存里的位置没有"地址"这个概念,所以不能取地址

#include<iostream>


using namespace std;
void fun() {
	#define AAA 222
};
class A{
	#define BBB 333//不能用来定义class专属变量,不具有封装性
public:
	#define CCC 444
};
int main() {
	enum  {aa=4,bb=1,cc=2,dd=3} b,c,d;
	b = aa;
	c = bb;
	d = cc;
	//cout<<&bb<<endl;//expression must be an lvalue or a function designator
	cout<<typeid(b).name()<<endl;//Z4mainEUt_
	cout<<&b<<endl;//0x7ffe363da5ac
	cout<<b<<endl<<c<<endl<<d<<endl;//4 1 2
	cout<<AAA<<endl;//222
	cout<<BBB<<endl;//333
	cout<<CCC<<endl;//444
}

小结:

涉及常量用const以及enum代替define, 函数宏则用inline代替define

条款3

#include<iostream>

using namespace std;
class A{
	mutable	char * text;
	int length;
public:
	A(char* p):text(p){length=1;};
	char& operator[](int pos) const{
		return text[pos];
	}
	void fun1(){}
	void fun2()const{}
	void print() const {
		//fun1();//只能调用const成员函数
		fun2();
		//length = 1;//const成员函数不能修改成员变量
		cout << this->text << endl;//读
		text = "12312";//mutable修饰的变量可以在const函数中更改
	}
	void getText() const {
		cout<<this->text<<endl;
	}
};

int main() {
	
	const A a("hello");
	a.print();//hello	 
	a.getText();//12312
}

条款4

构造函数初始化次序固定:先父类后子类,子类参数初始化按照声明的顺序
构造函数中,成员变量一定要通过初始化列表来初始化的几种情况

#include<iostream>

using namespace std;
class Base{
	int b_a;
	int b_b;
public:
	Base(int b_a,int b_b):b_a(b_a),b_b(b_b){cout<<"base ctor"<<endl;}
	//Base(){}
};
class Derived:public Base{
	int d_a;
	int d_b;
	//int &c;//引用变量必须在构造函数通过初始化列表初始化
public:
	Derived(int d_a,int d_b,int b_a,int b_b):d_a(d_a),d_b(d_b),Base(b_a,b_b)
	{
		cout<<"derived ctor"<<endl;
	}

};
int main() {
	Derived d(1,2,3,4);
	// base ctor
	//derived ctor
}

条款5

类成员有const变量或者引用,想要‘=’assignment操作必须要自己定义
拷贝引用会让两个变量指向一样的东西
const则不能被赋值

const int a = 1;//初始化
//a = 1;//不能被赋值

int a = 1;
int c = 4;
int &b=a;
b = c;//可以

int a = 1;
int c = 4;
int &b = a;
int &d = c;
b = d;//相同
cout << b << " "<< d <<endl;//4 4

int a = 1;
int c = 4;
const int &b=a;
//b = c;//不可
#include<iostream>

using namespace std;

class A{
	int a;
	const int b;
	int &c;
public:
	A(int a, const int b,int c):a(a),b(b),c(c){}
	
};
int main() {
	A a(1,2,3);
	A b(2,3,4);
	//b=a;//出错
	//main.cpp:10:7: error: non-static const member ‘const int A::b’, can’t use default assignment operator
	//main.cpp:10:7: error: non-static reference member ‘int& A::c’, can’t use default assignment operator
}

条款7

虚析构函数:一般类里带有虚函数,就要有个虚析构函数。
如果没有虚函数,就没必要写虚析构了,会增加对象体积(多个vptr)
别去继承STL容器类如string,vector,list,set,unordered_map因为他们没有虚析构函数

什么是抽象类

声明了纯虚函数的类,都成为抽象类。抽象类只能作为基类来派生新类,不能声明抽象类的对象
详细:c++抽象类

虚析构和纯虚析构

在这里插入图片描述

#include<iostream>
#include<thread>
#include<vector>
#include<string>
#include<queue>
#include<mutex>

using namespace std;

class animal
{
public:
	animal() { cout << "animal构造函数调用" << endl; }

	//能调用子类析构函数 来释放堆区的解决方法:

	//利用虚析构可以解决父类指针释放子类对象时不干净的问题
	//virtual ~animal() { cout << "animal虚析构函数调用" << endl; }

	//而如果想避免基类实例化,则可以将析构函数写成纯虚析构
	//但因为派生类(子类)不可能来实现基类(父类)的析构函数,
	//所以基类析构函数虽然可以标为纯虚,但是仍必须实现析构函数,
	//否则派生类无法继承,也无法编译通过。

	//纯虚析构 要声明也要实现(类外定义)
	//有了纯虚析构之后,这个类也属于抽象类,并无法实例化对象

	virtual  ~animal() = 0;//纯虚析构

	virtual void speak() = 0;//纯虚函数,必须在派生类中被实现
};

//纯虚析构函数必要类外定义
animal::~animal() { cout << "animal纯虚析构函数调用" << endl; }
//可写可不写
void animal:: speak() { cout << "父类纯虚函数" << endl; }

class cat : public animal
{
public:
	cat(string name)
	{
		cout << "cat构造函数调用" << endl;
		m_Name = new string(name);
	}

	virtual void speak() { cout << *m_Name << "小猫在说话" << endl; }

	~cat()
	{
		if (m_Name != NULL)
		{
			cout << "cat析构函数调用" << endl;
			delete m_Name;
			m_Name = NULL;
		}
	}

	string* m_Name;
};


void main()
{
	//父类指针指向了一个开辟在堆区的子类数据

	animal* a = new cat("Tom");
	
	//new一个在堆区的cat类型的对象,cat继承自animal
	//我们知道创建子类时会先创建父类

	//所以先调用了animal的构造函数
	//再是cat的构造函数


	//亿点细节:

		//在创建父类时,如果发现父类中有虚函数
		//编译器就会使用一个叫虚函数表的东西,保存所有的虚函数,包括纯虚函数
		//在这里,animal父类的虚函数表中,有两个虚函数

		/*
			class animal	size(4):
					+---
			0		| (vfptr)
					+---

			animal::$vftable@:
					| &animal_mata
					|  0
			0		| &animal::(dtor)
			1		| &animal::speak
		*/

		//&animal::(dtor)  就是animal的纯虚析构函数的地址
		//dtor是destructor的缩写,是析构函数的意思,中文直译叫"垃圾焚毁炉"
		//&animal::speak   就是speak虚函数的地址

		//接着,子类继承了这个表,重写了speak。

		/*
			class cat       size(8):
						+---
			 0      	| +--- (base class animal)
			 0      	| | {vfptr}
						| +---
			 4      	| m_Name
						+---

			cat::$vftable@:
					| &cat_meta
					|  0
					| &cat::(dtor)
			 0		| &cat::speak
		*/

		//这个dtor我不知道算不算重写,我觉的应该是的
		//但我不了解原理,所以搬了一个别人的解释:

		//当父类析构函数定义为虚函数后,子类默认就是虚析构函数,跟普通成员函数一样
		//现在将父类析构函数 定义为虚函数,子类再继承了 父类的虚函数表
		//子类的虚函数表中,就存在父类的 虚析构函数的地址,
		//但子类的析构函数 也是虚函数,所以重写了自己虚函数表中 父类虚析构函数的地址
		//变成了子类的虚析构函数地址
		//虽然父子的析构函数名字不一样,但是他们占同一个坑
		//(即父子析构函数在虚函数表中的位置是一样的,否则就不存在多态了)

		//用父类指针指向子类,并最后通过父类指针删除子类对象的时候,
		//发现animal类的析构函数是虚函数,就到子类对象的虚函数表里找析构函数
		//此时就会调用虚函表中 子类的虚析构函数 并很好的防止了内存泄漏

		//然后还有个问题,
		//既然子类的虚函数表中只有子类虚析构函数,那它怎样析构父类呢?
		//父类的析构函数又在哪呢?

		//其实在子类的析构函数中,底层包含着对父类析构函数的调用

		//原因我认为是:
		//对象都存放在栈中,创建子类对象先调用父类的对象,
		//父类对象压入栈,然后是子类对象入栈,由于栈是先进后出,
		//delete父类的时候就先调用子类对象,在调用父类对象



	a->speak();
	//因为子类的speak重写了父类的speak,父类指针调用的是子类的speak 

	delete a;
	//父类指针在析构的时候 不会调用子类中的析构函数
	//导致子类如果有堆区属性,出现内存泄漏

	//原因是指针a是animal类型的指针,也就是父类类型的指针
	//释放a时只进行animal类的析构函数。

	//但父类析构函数变成虚析构之后,这个问题就解决了

}
/*
output:
	animal构造函数调用
	cat构造函数调用
	Tom小猫在说话
	cat析构函数调用
	animal纯虚析构函数调用
*/

条款9

在这里插入图片描述

#include<iostream>
#include<vector>
using namespace std;

class Transaction//父类
{
public:
    Transaction() { logTransaction(); }
    virtual void logTransaction() const = 0;   
};
//这里纯虚函数必须被定义,否则报错(因为要调用父类的虚函数)
void Transaction:: logTransaction() const
{
    cout << "logTransaction log" << endl;
}
class BuyTransaction : public Transaction//子类1
{
public:
    BuyTransaction() {}
    virtual void logTransaction() const
    {
        cout << "BuyTransaction log" << endl;
    }
};

class SellTransaction : public Transaction//子类2
{
public:
    SellTransaction() {}
    virtual void logTransaction() const
    {
        cout << "SellTransaction log" << endl;
    }
};
int main() {
    BuyTransaction b;//定义一个子类
    //输出:logTransaction log
}

条款11

自我赋值不检查地址,会报错
delete p 不仅销毁当前对象的指针p,也销毁了要赋值对象的指针p,变成野指针

#include<iostream>
#include<vector>
using namespace std;
class A {
private:
    int* p;
public:
    A() { p = new int(1); }
    A& operator=(const A& a) {
        //if (this == &a)return *this;
        delete p;
        p = NULL;//delete指针后,一定要将其置空(原书没有这句,但是调试发现delete后p仍存在(空间仍在),只是指向的区域的值变得不确定)
        p = new int(*a.p);//成员函数内允许实例对象访问私有变量
        return *this;

    }
};
int main() {
    A a;
    a = a;//报错
}

C++ delete指针后依然可以访问的问题
在这里插入图片描述

条款12

当有类继承时,要完整实现拷贝构造和拷贝赋值。可以在子类拷贝构造中调用父类的拷贝构造(不能调用父类的拷贝赋值),子类拷贝赋值中调用父类的拷贝赋值(不能调用父类的拷贝构造)

条款15

share_ptr提供一个get成员函数,用来执行显示转换,也就是它会返回智能指针内的原始指针的复件

std::tr1::shared_ptr<Investment>pInv(createInvsetment());//智能指针
int fun(const Investment* pi);
int d = fun(pInv);//错误!类型不一样
int d = fun(pInv.get());//正确!

显示转换安全,但是每次都要调用get

class FontHandle{};
class Font {
	FontHandle f;
public:
	explicit Font(FontHandle f):f(f) {}
	~Font() {};
	FontHandle get()const { return f; }
};

隐式转换方便,但是不安全

class FontHandle{};
class Font {
	FontHandle f;
public:
	explicit Font(FontHandle f):f(f) {}
	~Font() {};
	operator FontHandle() { return f; }//隐式转换
};

隐式转换的例子

#include <iostream>

class Complex {
private:
    double real_;
    double imag_;

public:
    Complex(double real, double imag)
      : real_(real)
      , imag_(imag) {}

    // 转换构造函数
    Complex(double real)
      : real_(real)
      , imag_(0) {}

    // 隐式转换函数
    operator double() { return real_; }

    friend std::ostream& operator<<(std::ostream&, const Complex&);
};

std::ostream&
operator<<(std::ostream& os, const Complex& cp) {
    if (cp.real_) {
        os << cp.real_;
    }
    if (cp.imag_) {
        if (cp.real_) {
            os << '+';
        }
        os << cp.imag_ << 'i';
    }
    return os;
}

int
main(void) {
    Complex c1(1.2, 3.5);
    Complex c2 = 4.4; // 对 4.4 调用了转换构造函数
    double d = 1.8 + c1; // 对 c1 调用了类型转换函数
    std::cout << "c1 = " << c1 << std::endl;
    std::cout << "c2 = " << c2 << std::endl;
    std::cout << "d = " << d << std::endl;
    return 0;
}

条款17

考虑函数
void processWidget(std::tr1::shared_ptr <Widget>,int priority );
实现:
processWidget(std::tr1::shared_ptr<Widget>(new Widget),priority());

编译器可能的执行顺序:
1.执行new Widget
2.调用priority
3.调用tr1::shared_ptr构造函数

万一在执行第二步时抛出异常,此时new Widget返回的指针将会遗失,因为它未被置入tr1::shared_ptr内,引发资源泄漏!
解决办法:独立语句执行
std::tr1::shared_ptr<Widget>pw(new Widget);
processWidget(pw,priority());

条款18

条款18

cross-dll和智能指针

条款20

class A {//父类	
public:
	string name()const;
	virtual void display()const;
};
class B :public A {//子类
public:
	virtual void display()const;
};
//函数
void func(A a) {
	cout << a.name() << endl;
	a.display();
}


B b;
func(b);//pass-by-value,子类对象会被切割成父类,调用A::name()和A::display()绝对不会是B::name()和B::display()

正确的方法是pass by reference-to-const

void func(const A& a) {//实现多态!
	cout << a.name() << endl;
	a.display();
}

总结:
1.pass by reference通常意味着真正传递的是指针。因此如果有个对象属于内置类型(如int)或者STL的迭代器和函数对象,通常还是pass by value效率高
2.其他情况还是pass-by-reference-to-const好,如上代码还可以避免切割问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值