类成员变量中存在引用,const,和指针类型时需要注意的事项

背景知识

编译器默认生成函数的规则如下:

1.定义一个类时,如果自己没有声明,那么编译器会自动帮助生成一个拷贝构造函数(copy construction),赋值操作符(copy assignment),析构函数(deconstruction)。

2.如果没有声明任何构造函数(包括拷贝构造函数),编译器会帮助声明一个默认构造函数。

构造函数(包括编译器生成的默认构造函数)的执行包括两个阶段:

1.初始化阶段

2.构造函数体内代码执行构造的阶段

构造函数执行的两个阶段非常重要,在初始化阶段,如果类中存在类类型的成员变量,那么会调用这个类类型的成员变量的默认构造函数来初始化这个成员变量,如果是内置类型,那么可能会对内置类型的变量初始化,也可能不会对其初始化(这点和内置类型的变量是全局变量还是局部变量有关系)。

弄明白了初始化阶段的工作后就明白了构造函数体内执行的代码都是赋值操作而不是初始化操作了。因为初始化操作在构造函数体的代码执行之前就已经完成了。我们也可以对初始化阶段的工作进行控制,这就是C++中的构造函数初始化列表。C++中构造函数初始化列表就是控制初始化阶段的,如果没有提供初始化列表,那么会调用成员变量中的默认构造函数来初始化成员变量。

1.构造函数执行的两个阶段

以一个例子来感受下构造函数执行的两个阶段,自定义两个类,让其中一个类作为另外一个类的成员变量:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

class Bitmap
{
	public:
			//默认的构造函数
			Bitmap()
			{
				std::cout<<"bitmap construction:"<<this<<std::endl;
			}

			//拷贝构造函数
			Bitmap(const Bitmap & rhs)
			{
				std::cout<<"bitmap copy construction"<<std::endl;
			}

			//赋值操作符
			Bitmap& operator=(const Bitmap & rhs)
			{
				std::cout<<"operator assignment:"<<this<<" = "<<&rhs<<std::endl;
			}
};

class Widget
{
	public:
			//自定义的构造函数,用来测试构造函数执行的两个过程,为了减少一次Bitmap拷贝构造函数的调用,这里传递引用
			Widget(Bitmap &bitmap)
			{
				b=bitmap;
				std::cout<<"Widget(Bitmap &b) construction:"<<&b<<std::endl;
			}

			//因为自己定义了构造函数,所以编译器不会再为我们生成构造函数了,所以需要自己定义一个默认的构造函数
			Widget()
			{
				std::cout<<"Widget construction:"<<&b<<std::endl;
			}

			private:
			Bitmap b;//成员变量为类类型时在构造函数中才会调用Bitmap的构造函数

};

int main()
{
	Bitmap bb;
	Widget w1(bb);
	return 0;
}

上面的代码执行后的结果如下:

其中:

Bitmap bb; 这行代码执行后打印出bitmap construction:ox28ff2f,表示创建的这个对象bb的地址是0x28ff2f

Widget w1(bb);这行代码执行w1的构造函数包括两个阶段:

1.初始化阶段,调用w1的成员变量Bitmap b的默认构造函数初始化对象b,这时又会调用一次构造函数来创建Widget的成员变量b,它的地址是0x28ff2e。

2.构造阶段,调用w1的第一个构造函数内的执行代码:

b=bitmap;//此时会调用Bitmap的赋值操作符,打印出operator assignment :ox28ff2e=0x28ff2f,这里注意ox28ff2e是初始化阶段生成的对象的地址,0x28ff2f是我们传递给Widget构造函数实参的地址。赋值操作只是将bitmap对象的内容拷贝到                        b对象中,不会更改b对象的地址。

然后执行输出操作,打印出来的是在初始化阶段创建的对象的地址

如果将Widget中Bitmap对象的初始化工作放到构造函数初始化列表中执行,那么就只会有一次拷贝构造函数的调用,而不会像上面调用一次默认构造函数,再调用一次赋值操作符了。代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

class Bitmap
{
	public:
			//默认的构造函数
			Bitmap()
			{
				std::cout<<"bitmap construction:"<<this<<std::endl;
			}

			//拷贝构造函数
			Bitmap(const Bitmap &rhs)
			{
				std::cout<<"bitmap copy construction:"<<this<<std::endl;
			}

			//赋值操作符
			Bitmap& operator=(const Bitmap & rhs)
			{
				std::cout<<"operator assignment:"<<this<<" = "<<&rhs<<std::endl;
			}
};

class Widget
{
	public:
			//自定义的构造函数,用来测试构造函数执行的两个过程,为了减少一次Bitmap拷贝构造函数的调用,这里传递引用
			Widget(Bitmap& bitmap):b(bitmap)
			{
				std::cout<<"Widget(Bitmap &b) construction:"<<&b<<std::endl;
			}

			//因为自己定义了构造函数,所以编译器不会再为我们生成构造函数了,所以需要自己定义一个默认的构造函数
			Widget()
			{
				std::cout<<"Widget construction:"<<&b<<std::endl;
			}

			private:
			Bitmap b;//成员变量为类类型时在构造函数中才会调用Bitmap的构造函数

};

int main()
{
	Bitmap bb;
	Widget w1(bb);
	return 0;
}

输出结果如下:

Bitmap bb;//这条语句会调用默认的构造函数

Widget w1(bb);//因为Widget带有Bitmap参数的构造函数中使用了初始化列表,所以在初始化阶段调用了拷贝构造函数来创建成员变量b,它的地址是ox28ff2e。

2.如果自定义了构造函数,那么一定也要加上默认构造函数

其实上面的例子已经可以说明问题了,就是Widget类中的Bitmap成员变量在初始化阶段自动调用了默认的构造函数,如果一个类中不存在默认的构造函数(即自己定义了构造函数但是忘记添加默认的构造函数),那么这个类是其他类的成员变量时必须在构造函数初始化列表中提供参数进行初始化,否则会由于在初始化阶段需要调用默认构造函数而却没有而导致构造失败。还是写上一个例子,在上面的类中在加上一个Pane类,注意Widget类的构造函数也做了一些改变:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

class Bitmap
{
	public:
			//默认的构造函数
			Bitmap()
			{
				std::cout<<"bitmap construction:"<<this<<std::endl;
			}

			//拷贝构造函数
			Bitmap(const Bitmap &rhs)
			{
				std::cout<<"bitmap copy construction:"<<this<<std::endl;
			}

			//赋值操作符
			Bitmap& operator=(const Bitmap & rhs)
			{
				std::cout<<"operator assignment:"<<this<<" = "<<&rhs<<std::endl;
			}
};

class Widget
{
	public:
			//注意这里没有传递引用了,不然下面Pane类的初始化列表就不能那样写了
			Widget(Bitmap bitmap):b(bitmap)
			{
				std::cout<<"Widget(Bitmap &b) construction:"<<&b<<std::endl;
			}
			private:
			Bitmap b;//成员变量为类类型时在构造函数中才会调用Bitmap的构造函数

};

class Pane
{
		public:
				//由于Widget没有默认的构造函数,所以需要在Pane的构造函数初始化列表中提供参数进行
				//初始化,这里没有为其提供初始化式,会导致构造失败
				//把下面的注释解开就是正常的写法了,提供了w类的初始化式
				Pane()//:w(Bitmap())
				{
					std::cout<<"pane construction:"<<this<<std::endl;
				}

		private :
				Widget w;
};

int main()
{
	Pane p;
	return 0;
}

当Pane不提供初始化式时,即向上面那样,编译器会提示报错:

当将Pane类的初始化列表中的注释去掉后就可以正常构造了,执行结果如下:

3.引用类型,const类型的成员变量必须在初始化列表中进行初始化

由于C++中引用类型和const类型必须在声明的时候就进行初始化,并且它不能再指向其他变量。所以根据上面构造函数初始化阶段的说明,引用类型和const类型必须在初始化列表中初始化,不能写在构造函数内。关于const关键可以参考之前的这篇博客

template<class T>
class NamedObject
{

		public:
				//由于str和objectVale为引用或const类型,所以必须在初始化列表中进行初始化
				NamedObject(std::string & s,const T &t)
					:str(s),objectValue(t)
				{
					//str=s;//不能在函数体内对引用类型和const类型初始化,函数体内的是赋值操作而不是初始化操作
					std::cout<<"const invoke!"<<std::endl;
				}
		private:
				 std::string & str;
				 const T objectValue;
};

4.引用类型,const类型不能使用编译器默认生成的赋值操作符

由于编译器默认生成的赋值操作符只是简单的执行赋值操作,而引用类型或const类型的变量是不能指向其它变量的,所以如果一个类中存在引用或const类型的变量,那么需要自己定义赋值操作符,否则会出现编译错误。同样以上面的那个类为例,必须为上面的那个类自己定义赋值操作符,否则编译都会通不过:

template<class T>
class NamedObject
{

		public:
				//由于str和objectVale为引用或const类型,所以必须在初始化列表中进行初始化
				NamedObject(std::string & s,const T &t)
					:str(s),objectValue(t)
				{
					std::cout<<"const invoke!"<<std::endl;
				}

				//由于成员变量中存在引用和const类型,所以必须要
				//自定义operator=操作符,因为引用和const类型都不能
				//重新赋值
				NamedObject& operator=(const NamedObject & rhs)
				{
						std::cout<<"operator= invoke!"<<std::endl;
				}

		private:
				 std::string & str;
				 const T objectValue;
};

int main()
{
	//注意const char *只能转换成std::string而不能转换成std::string&,所以下面两步不能合并成一步
	std::string s="hello";
	NamedObject<int> obj1(s,1);
	NamedObject<int> obj2(obj1);
	obj2=obj1;
	return 0;
}

5.类中存在指针类型的对象

当类中存在指针类型的变量时,需要注意编译器不会自动为其调用类的构造函数,这时需要手动设置指针为NULL或调用new为其动态创建对象。

同时也需要注意编译器自动创建的赋值操作符和拷贝构造函数是否符合自己的要求了。因为编译编译器自动创建的赋值操作符和拷贝构造函数只是简单的进行赋值操作。

下面是一个易懂但是不具备异常安全和自我赋值安全的赋值操作符定义:

#include <stdlib.h>
#include <stdio.h>
#include <iostream>

class Bitmap
{
	public:
			Bitmap()
			{
				std::cout<<"bitmap construction:"<<this<<std::endl;
			}
};

class Widget
{
	public:
			Widget()
			{
				pb=new Bitmap();
				std::cout<<"Widget construction:"<<this<<std::endl;
			}

			//注意这种写法是异常不安全和自我赋值不安全的,下面会有改进措施
			Widget & operator=(const Widget & w)
			{
				delete pb;
				pb=new Bitmap(*w.pb);//注意.运算符优先级比*运算符高
				std::cout<<"operator = "<<std::endl;
				return *this;
			}
	private:
			Bitmap *pb;//类型为指针时不会调用Bitmap的构造函数

};

int main()
{
	Widget w1;
	Widget w2;
	w2=w1;
	return 0;
}
时间: 2024-08-27 17:16:00

类成员变量中存在引用,const,和指针类型时需要注意的事项的相关文章

C++---类成员变量定义为引用

摘要:类成员变量是可以定义为引用类型的,但是我们需要注意一下用法 note1:在类中定义引用变量, 必须要在初始化列表中初始化该成员变量(const 类型数据成员也必须在初始化列表中进行初始化) #include <iostream> using namespace std; class A { public: A(int k):a(n){ // 必须要在初始化列表中进行初始化 n = k; cout << a <<endl; } int get() const { r

C++ 定义引用型类成员变量

作者 : 卿笃军 1)早在学习C语言的时候,我们就知道,在定义结构体的时候,不能在结构体中定义该结构体类型的变量: struct node { int a; // struct node b; //错 struct node *next; //对 }; 因为,该结构体还在定义中....不知道结构体里面具体有什么内容,所以无法定义对象b.但是可以定义指针*next. 2)现在,C++里面多出来了一个叫引用的东东(很强大): 引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样.

php 类 成员变量 $this-&gt;name=&#39;abc&#39;

<?php class test { public function getName() { $this->name='abc'; echo $this->name; } }$a=new test();$a->getName(); 1.$this->name='abc'对应的BNF范式 expr_without_variable: variable '=' expr { zend_check_writable_variable(&$1); zend_do_assign

变量,final常量,类成员变量,static关键字,实例变量,静态变量,局部变量

1.常量:又称为final变量,在整个程序中仅能被赋值一次 final int num = 1215; num = 1216; // 错误,仅能赋值一次 2.类成员变量:不在方法中定义的变量即为成员变量,在方法体内定义的变量则为局部变量 成员变量含义:对象的属性 public class Book{ int id; string name; //两个成员变量 public String getName(){ int id = 0;  //局部变量,必须进行赋值或初始化 } } 3.static关

Objective-C类成员变量深度剖析

目录 Non Fragile ivars 为什么Non Fragile ivars很关键 如何寻址类成员变量 真正的“如何寻址类成员变量” Non Fragile ivars布局调整 为什么Objective-C类不能动态添加成员变量 总结 看下面的代码,考虑Objective-C里最常见的操作之一——类成员变量访问. - (void)doSomething:(SomeClass *)obj { obj->ivar1 = 42; // 访问obj对象的public成员变量 int n = sel

类成员变量初始化的问题

class window{ window(int maker){ System.out.println("window"+maker); } } class House{ window w1 ;//new window(1); window w3 = new window(3); House(){ System.out.print("House"); w3 = new window(33); } window w4 = new window(4); } class

PHP 类成员变量规范得一些理解。

今儿写代码是发现一个问题: PHP 类 成员变量不能接受方法得返回值 做以下测试: bin_2.php <?php /** * Created by PhpStorm. * User: Administrator * Date: 14-12-4 * Time: 下午9:45 */ header("Content-type:text/html;charset=utf-8"); require_once('bin_1.php'); require_once('bin_3.php');

Qt一个工程调用另一个工程的类成员变量

一句两句话已经不能表达现在的激动情绪了,唯有感叹知识的博大精深,并把感叹转变为文字. 同一个工程调用其他类成员变量非常简单. 如: 定义 Test1.h中申明成员变量 class A { public: double m_fTest; }; Test1.cpp中改变成员变量的值. m_fTest = 265.78; 然后再类B中调用类A的成员变量m_fTest,并需求类A中改变该值时,类B中也要随之改变: 调用方法:包涵类A的头文件 #include "test1.h" 然后在调用处声

Python的类成员变量默认初始值的坑

问题发现:一个循环内,缺省值初始化同名变量,其中的list成员不是空,会延续之前同名变量的值. 示例代码: # Define class class Variant(): # use def __init__(self, price = 500, description = 'default description', values = ['', '', '']): self.price = price self.description = description self.values = v