【Effective C++]之良好的编程习惯和细节(1)

使用C++进行编程时,一些容易疏忽的小细节可能会导致代码错误或产生二义性

所以借助Effective这本书浅谈一些关于C++编程过程中需要注意的细节

只有养成良好的编程习惯才能使代码更具健壮性和可读性

1.关于何时使用explicit

explicit是显露的意思,在函数使用中代表可以进行显式类型转换,禁止进行隐式类型转换

显式类型转换就是我们平时所说的强转,即将一个数据类型加上括号强制转换为另外一个数据类型

隐式类型转换则是编辑器根据可允许范围内对数据进行的隐蔽的数据类型转换,我们是看不见的

具体的转换细节这里不进行详述

由显式转换和隐式转换的特点可知,显式转换是自己需要的,而隐式转换是编辑器自己进行的

当我们不需要隐式类型转换时,就最好禁止编辑器进行自主的转换

引用Effective C++内的话就是:

除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把他声明为explicit 

 下面是一个隐式转换的例子

class A
{
public:
       //默认构造
       A()
       {
       }


       //有参构造
       A(int x)
       {
         cout<< x <<endl;
       }
};

int main()
{
   A a=10.5;
   A a1='a';

   system("pause");
   return 0;
}

 我们为该对象赋值为float或者char类型时,都可以进行隐式类型转换为int类型,所以不会报错

//有参构造
  A(int x,double y)
{
  cout<< x <<endl;
};

int main()
{
  A a=10.5;
  A a1='a':
}

但隐式转换不能多个参数,多个参数编辑器便不知道该转换为什么类型,所以会报错

若不是有意进行隐式转换便会降低可读性,不知道为何会进行该类型的参数传递

所以当我们不想进行隐式类型转换时就可以在函数前加上explicit

      //有参构造
       explicit A(int x)
       {
         cout<< x <<endl;
       }

此时再进行不同类型的参数传递编辑器就会报错,但支持显式类型转换


2.自定义类型赋值调用哪些函数

对于内置数据类型,编辑器已经为我们设置好了等号操作符,所以可以直接调用=

对于自定义数据类型,则需要我们自己重定义等号操作符,如两个类的操作(不管是相加还是赋值等操作都需要对操作符进行重载),否则编辑器就不知道如何对两个类的数据类型进行操作

那么对于一个类的赋值(即类A=类B)调用了哪些函数呢

第一个我们已经知道了,就是重载操作符函数

第二个则是拷贝构造函数,对B类进行浅拷贝操作赋值给A类

如何区别具体调用了哪一个

看下面的例子

	//拷贝构造
	A(const A&a)
	{

		cout << "拷贝构造函数的调用" << endl;
	}

	//有参构造
     A(int x)
	{
		cout << x << endl;
	}
};

int main()
{
	A a1(10);
	A a2(20);
	A a3 = a1;
	cout << "---" << endl;
	a3 = a2;

 

 由结果看出

cout以上的代码会调用拷贝构造函数,cout以下的代码会调用重载 =操作符

由此我们得出结论:当新建一个对象时,赋值操作是由拷贝构造函数完成

                                当这个对象已经存在时,赋值操作由重载=操作符完成 


 3.对象使用前为什么必须初始化

在编程中,我们有时候会只声明了一个对象而忘记或者没有有意识的对其进行初始化操作

这会导致某些不明确的行为,在某些平台上该行为便可能会造成程序终止

而对象的初始化又分为内置类型和自定义类型

内置类型初始化

int x=0;
string y=" ";

我们需要有意识的对我们需要使用的内置对象进行初始化操作,而不是等到系统报错再修改

自定义类型初始化

当我们定义了一个类,就需要对类内的属性进行初始化操作,我们可以将声明和初始化合并起来

但其缺点便是不能对初始化的值进行修改,所以为了后续程序的使用最好将属性的声明和初始化

进行分离

声明和初始化一起的操作

class People
{
public:
	int m_age = 0;
	string m_name = "hhh";


	//People(string name);
};

声明和初始化分开的操作

class People
{
public:
	string m_name;
    string m_id;

	People(const string &name,const string &id);
};

People::People(const string &name, const string &id)
{
	this->m_id = id;
	this->m_name = name;
}

由此我们就可以对该对象的初始化进行设置,如游戏的初始设定值

但其实这并不是真正意义上的自定义类型的初始化操作

C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前

而上面对象的初始化是在构造函数本体内进行的 

所以这并不是一个初始化操作,而是赋值操作

为了实现更高效率的真正意义的初始化,我们可以利用成员初值列的操作 

class People
{
public:
	string m_id = " ";
	string m_name = " ";
    
	People(const string &id,const string &name);
};

People::People(const string &id, const string &name):m_id(id),m_name(name)
{
	
}

成员初值列的操作使对象的初始化发生在构造函数本体前

所以不会调用这个构造函数,而是直接调用编辑器自定义的拷贝构造函数

将要初始化的值拷贝到对象属性上完成对该对象的初始化

若是采用一开始的赋值操作则不仅会调用拷贝构造函数,还会调用该构造函数

当函数内部具备复杂的内容时,调用函数的时间就可以忽略不计

当函数内部只有简单的内容或是一个空函数时,调用函数的时间就会被凸显,效率差异开始体现

一次函数调用就是一次进栈和出栈,当频繁使用该对象时,就会多次调用构造函数从而进行多次进栈和出栈,由此会造成时间资源的浪费

所以建议进行对象初始化时,最好采用成员初值列进行初始化操作


4.编辑器默默编写调用了哪些函数

C++为我们的类默认编写了default构造函数,析构函数,copy构造函数,copy assignment操作符

default构造函数在调用该类对象时就会自动调用,是最先被调用的

析构函数在调用该对象结束时会被自动调用

copy构造函数在完成类对类的赋值时调用

copy assignment操作符(即=)在完成类对类的赋值时调用(上文2.已经解释二者区别)

注意:当该类的父类将copy assignment操作符声明为private(私有属性)时,编辑器将拒绝为该类生成一个copy assignment操作符,需要使用时,只能由自己进行定义

当我们编写了一个空类时,只要经过编辑器的编译,就会自动为我们生成这4个函数,并在需要的时候调用他们

当我们自己定义了这些函数时,编辑器就不会再为我们提供自己定义过的函数

什么时候我们需要自己定义这些函数?

1.default构造函数在我们需要对类内属性进行初始化或赋值时需要自定义,当类内属性为private时,类外访问不到(排除友元的情况)时,就需要借助构造函数对类内属性进行赋值操作

2.析构函数在我们开辟了堆区空间需要对堆区空间进行释放时需要自定义,所以我们需要自定义析构函数对指向堆区的指针进行操作,避免内存泄漏

3.copy构造函数在我们遇到浅拷贝问题时需要自定义

浅拷贝即拷贝对象里有指针存在指向堆区空间,浅拷贝只对这个指针进行拷贝赋值,所以只复制了指针,导致两个指针指向同一份堆空间

当我们进行堆区释放时,便会对这份堆区进行重复释放导致非法访问

所以需要自定义copy构造函数来开辟一块堆区空间使拷贝过来的指针指向新空间

4.copy assignment操作符在父类私有化操作符时需要自定义

当我们不想使用编辑器自动生成的函数时,就应该明确的拒绝

为什么最好拒绝?因为当这个类很重要时,你又未定义这些默认函数,所以外界的使用者就有机会对该类进行拷贝操作(默认构造和析构都是没有危害的),这是一种隐含的危险行为

由于不管你是否对copy构造函数和copy assignment进行了声明,只要外界使用了拷贝操作,编辑器都会为其声明并使其被调用成功

为了防止此操作,我们可以将copy构造函数和copy assignment进行声明并令其具有private属性,虽然成员函数和友元函数仍可以访问该类的私有内容,但我们可以对这两个函数进行空实现,由此来实现对该类内容的保护

引用Effective的忠告

为驳回编辑器自动提供的机能,可将相应的成员函数声明为private并且不予实现

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值