C++面向对象- -类和对象的使用(一)

这部分算是正式接触了类和对象,涉及到它们基础的应用。


目录

构造函数对类对象进行初始化

1、对象的初始化

2、构造函数实现数据成员的初始化

3、带参数的构造函数

4、参数初始化表对数据成员的初始化

5、构造函数的重载

6、使用默认参数的构造函数

析构函数

构造函数与析构函数

对象数组


 

构造函数对类对象进行初始化

当对象在创建时获得了一个特定的值,我们说这个对象被初始化。初始化不是赋值,初始化的含义是创建变量赋予其一个初始值,而赋值的含义是把当前值擦除,而以一个新值来替代。在汇编语言中,为变量分配空间时,初始化过的变量的初值位于可执行文件代码段数据后,会占用一定空间,不必要的初始化会造成磁盘空间的浪费。而在C/C++语言等高级语言中,为每一个变量赋初值被视为良好的编程习惯,有助于减少出现Bug的可能性。因此,是否对不必要的变量初始化依情况而定。初始化时是按照数据成员在类里被声明的顺序进行初始化

1、对象的初始化

对象的初始化和之前的那些定义变量时的初始化不一样,它不能在类声明中对数据进行初始化,因为类并不是一个实体,而是一种抽象类型,不占用存储空间,无法容纳数据。

class Time{
    int hour=0;
    int min=0;
    int sec=0;
};

如果一个类中所有的数据成员都是公用的,那么在定义对象时可以对数据成员进行初始化。但一般来说类中的数据成员都是私有的,因为要保持信息隐蔽的特性,所以这种方法基本上不会有。

class Time{
   public:
        int hour ;
        int min ;
        int sec ;
};

Time t= {0,0,0};    //此处注意是中括号,和后边构造函数初始化的表示不一样。

 

2、构造函数实现数据成员的初始化

构造函数是一种特殊的成员函数,与其它成员函数不同,不需要用户调用,也不能被用户调用,而是在建立对象时自动执行。构造函数的名字必须是与类名一样,不能任意命名,以便系统能识别它作为构造函数处理。且不具有任何类型,不返回任何值,只是对对象进行初始化

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(){
			hour=0;
			min=0;
			sec=0;
		}
}; 

在主函数执行时,一旦类建立对象,该构造函数将会自动执行,执行构造函数的过程中对 对象 中的数据成员赋初值,赋值在构造函数体中实现。另外还可以在类内仅声明构造函数,在类外对构造函数进行定义,只需加上  类名 和域限定符 “::” 即可。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time();
}; 
Time :: Time(){
	hour=0;
	min=0;
	sec=0;
}

当类建立对象时,系统开始为这个对象分配存储单元,此时便执行构造函数,每建立一个对象,就调用一个构造函数。构造函数是定义对象时由系统自动执行的,而且只能执行一次,构造函数一般声明为 public 。另外还可以用一个类对象对另一个类对象进行初始化

Time t1 ;
Time t2=t1 ;

在构造函数的函数体中可以有其他语句,像 “cout···”等语句,但一般不提倡在构造函数中加入与初始化无关的内容,以保持程序的清晰。如果用户自己没有定义构造函数,系统会自动生成一个构造函数,只是这个构造函数体是空的,也没有任何参数,不执行任何初始化操作

3、带参数的构造函数

在调用不同对象的构造函数时,从外面将不同的数据传递给构造函数,以实现不同的初始化,即在建立对象时同时指定数据成员的初值。如下:

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(int h,int m,int s){
			hour=h ;
			min=m ;
			sec=s ;
		}
}; 
int main(){
	Time t(0,0,0);
}

这种初始化对象的方法使用起来很方便,也很直观,从定义的语句中就能直接看到数据成员的初值。

 

4、参数初始化表对数据成员的初始化

参数的初始化表法算是带参数的构造函数的一种,不过减少了函数体的长度,使结构体显得精炼简单,可以直接在类体中定义构造函数,尤其当需要初始化的数据成员较多时更具有优越性。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(int h,int m,int s):hour(h),min(m),sec(s){}
		//Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
}; 
int main(){
	Time t(0,0,0);
}

一般参数初始化表中的中括号内是空的,不执行任何语句。

 

5、构造函数的重载

在一个类中可以定义多个构造函数,这些构造函数具有相同的名字,参数的个数或参数的类型不相同,以便为对象提供不同的初始化的方法。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(){		//无参数的构造函数 
			hour=0;
			min=0;
			sec=0;
		}
		Time(int h,int m,int s):hour(h),min(m),sec(s){}	//有参数的构造函数,并用初始化表进行初始化。 
}; 
int main(){
	Time t1;
	Time t2(0,0,0);
}

建立对象时不必给出实参的构造函数称为 默认构造函数 ,显然无参数构造函数属于默认构造函数。一个类中只能有一个默认构造函数,否则的话系统将不知道去调用哪个构造函数进行初始化。另如果用户未定义构造函数,则系统会1自动提供一个默认构造函数,但它的函数体是空的,不起初始化作用。如果用户希望在创建对象时就能使数据成员有初值,就必须自己定义构造函数。虽然在一个类中有多个构造函数,但对于一个对象来说,建立一个对象时只调用其中一个构造函数,并非每个构造函数都被执行。另外在建立对象时如果选用的是无参构造函数,注意正确的书写定义对象的语句:

Time t1;
Time t2();	//错误 

 

6、使用默认参数的构造函数

在声明构造函数时指定默认值而不是在定义构造函数时给出其默认值,声明构造函数时,形参名可以省略。

Time (int =0 ,int =0,int =0);

在建立对象时不必给出实参,但一个类中只能有一个默认构造函数,为了避免调用时的歧义性,另外如果在一个类中定义了全部是默认参数的构造函数后,不能再定义重载构造函数,即此时类中只能存在一个默认参数的构造函数。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}	//有参数的构造函数,并用初始化表进行初始化。 
		Time(){		//无参数的构造函数 
			hour=0;
			min=0;
			sec=0;
		}
		Time(int h,int m){
			hour=h;
			min=m;
		} 
}; 
int main(){
	Time t1;    //是调用第一个还是第二个?
	Time t2(1,1);    //是调用第一个还是第三个?
}

另外如果构造函数中的参数并非全部为默认值时,要根据1具体情况而定。

Time();
Time(int , int m=0, int s=0);
Time(int , int)
	

Time t1;	//调用第一个,无参构造函数 
Time t2(1);	//调用第二个,实参与形参从左到右一一对应 
Time t3(1,1);	//出现歧义 

所以我们一般不同时使用构造函数的重载和有默认参数的构造函数,出现二义性的可能性大。

 

析构函数

析构函数的功能与构造函数的功能相反,它的名字是 类名的前面加一个“~”符号,“~” 在这里是表示位取反运算符 ,可以想到析构函数是与构造函数作用相反的函数。

对象的生命周期结束时,也是自动执行析构函数。有以下几种情况:

  1. 如果在一个函数中定义了一个对象(局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
  2. 静态(static)局部对象在函数调用结束时对象并不释放,因此也不用调用析构函数,只有在 main 函数结束或者调用 exit 函数结束程序时,才调用 static 局部对象的析构函数。
  3. 如果定义了一个全局的对象,则在程序的流程离开其作用域时(如 main 函数结束或调用 exit 函数),调用该全局的对象的析构函数。
  4. 如果用 new 运算符动态地建立了一个对象 ,当用 delete 运算符释放该对象时,先调用该对象的析构函数。

析构函数的作用并不是删除对象,而是撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用(比如你在构造或者做某个程序的时候,开辟了内存空间(如数组),如果没有在析构之前释放它,则内存不会自动释放,会造成内存泄露)。程序设计者要事先设计好析构函数,以完成所需的功能,主要对象的生命周期结束,程序就自动执行析构函数来完成这些工作。

析构函数不返回任何值,没有函数类型,也没有函数参数,故它不能被重载。一个类可以有多个构造函数,但只能有一个析构函数。析构函数的作用并不仅限于释放资源方面,它还可以被用来执行 “用户希望在最后一次使用对象之后所执行的任何操作”,如输出有关的信息。

一般情况下,类的设计者应当在声明类的同时定义析构函数,以指定如何完成 “清理” 的工作。如果用户没有定义析构函数,系统也会自动生成一个析构函数,但它只是徒有析构函数的名称和形式,实际上什么操作都不执行。想让析构函数完成任何工作,都必须在定义的析构函数中指定。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
		void show(){
			cout<<"时间是: "<<hour<<":"<<min<<":"<<sec<<endl;
		}
		~Time(){
			cout <<"清理完成。"<<endl;
		}
}; 
int main(){
	Time t;
	t.show();
}

主函数中定义的对象是局部的,它的生命周期随着主函数的结束而结束,在撤销对象之前的最后一项工作是调用析构函数。

构造函数与析构函数

二者之间的顺序是:先构造的后析构,后构造的先析构。等同于 栈 :先进后出

归纳一下系统在什么时候调用构造函数和析构函数:

  1. 如果在全局范围内定义对象(即在所有函数之外定义的对象),那么他的构造函数在本文件模块中的所有函数(包括 main 函数)执行之前调用。但如果一个程序包括多个文件,而在不同的文件中都定义了全局变量,则这些对象的构造函数的执行顺序是不确定的。当 main 函数执行完毕或调用 exit 函数时(此时程序终止),调用析构函数。
  2. 如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果对象所在的函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
  3. 如果在函数中定义静态(static)局部对象,则只在程序第一次调用此函数定义对象时调用构造函数一次,在调用函数结束时对象并不释放,因此也不调用析构函数,只有在 main 函数结束或调用 exit 函数结束程序时才调用析构函数。

在有静态对象时,其构造函数和析构函数的调用顺序不一定就是 “先进后出” ,因为对象间的存储类别不一样,生命周期不一样。

 

对象数组

数组不仅可以由简单变量组成(例如整型数组中的每个元素都是整型变量),也可以由类对象组成(对象数组的每一个元素都是同类的对象)。

如果构造函数有多个函数,则不能用在定义数组时直接提供所有实参的方法,因为一个数组有多个元素,对每个元素要提供多个实参,如果再考虑到构造函数有默认参数的情况,很容易造成实参与形参的对应关系不清晰,出现歧义性。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
		void show(){
			cout<<hour<<":"<<min<<":"<<sec<<endl;
		} 
}; 
int main(){
	Time t[3] = {1,2,3};
	t[0].show();
}

这段代码就是出现了歧义性,编译系统并不是把这三个全作为第一个对象的实参,而是这三个实参分别作为这三个数组元素的第一个实参。编译系统只为每个对象元素的构造函数传递一个实参,所以在这样定义数组时提供的实参个数不能超过数组元素个数。

如果想对每个数组对象元素都进行初始化,那么在建立数组时,分别调用构造函数,对每个元素初始化,每一个元素的实参分别用括号括起来,对应构造函数的一组实参,这样就不会再混淆。

class Time{
		int hour ;
		int min ;
		int sec ;
	public:
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
		void show(){
			cout<<hour<<":"<<min<<":"<<sec<<endl;
		} 
}; 
int main(){
	Time t[3] = {
		Time(1,1,1),        //元素之间仍用逗号作为分隔
		Time(2,2,2),
		Time(3,3,3)
	};                    //注意分号!!!
	t[0].show();
}

猜你喜欢

转载自blog.csdn.net/qq_43305922/article/details/85376238