第二周 类和对象基础

277 阅读5分钟

在类的外面写成员函数

构造函数

成员函数的一种,一类特殊的成员函数

  • 名字与类名相同,可以有参数,不能有返回值(void也不行)

  • 作用是对对象进行初始化,如给成员变量赋初值

  • 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数

  • 默认构造函数无参数,不做任何操作

名字和类名一样

  • 如果定义了构造函数,则编译器不生成默认的无参数的构造函数

  • 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数

  • 一个类可以有多个构造函数

为什么需要构造函数?

  • 构造函数执行必要的初始化工作,有了构造函数,就不必专门再写初始化函数,也不用担心忘记调用初始化函数。

  • 有时对象没被初始化就使用,会导致程序出错。

    #include using namespace std; class Complex { private: double real,imag; public: Complex(double r,double i =0); }; //编译器自动生成默认构造函数

    Complex::Complex(double r,double i) { real = r; imag = i; } //Complex c1;//error,缺少构造函数的参数 //Complex * pc = new Complex;//error,没有参数 Complex c1(2);//OK Complex c1(2,4),c2(3,5); Complex * pc = new Complex(3,4);

    int main(int argc, char** argv) { return 0; }

可以有多个构造参数,参数个数或者类型不同

#include <iostream>
using namespace std;
class Complex
{
	private:
		double real,imag;
	public:
		void Set(double r,double i);
		Complex(double r,double i);
		Complex(double r);
		Complex(Complex c1,Complex c2);
}; //编译器自动生成默认构造函数 ,三个构造函数形成重载的关系

Complex::Complex(double r,double i)
{
	real = r;
	imag = i;
}

Complex::Complex(double r)
{
	real = r;
	imag = 0;
}

Complex::Complex(Complex c1,Complex c2)
{
	real = c1.real+c2.real;
	imag = c1.imag+c2.imag;
}

Complex c1(3),c2(1,0),c3(c1,c2);
//c1 = {3,0},c2 = {1,0},c3 = {4,0};

int main(int argc, char** argv) 
{
	return 0;
}

构造函数最好的是public,private构造函数不能直接用来初始化对象

#include <iostream>
using namespace std;
class CSample
{
	private:
		CSample()
		{

		}
};

int main() 
{
	//CSample Obj;//err.唯一构造函数是private 
	return 0;
}

构造函数在数组中的使用

#include <iostream>
using namespace std;

class CSample
{
	int x; 
	public:
		CSample()
		{
			cout << "Constructor 1 Called" <<endl;
		}
		CSample(int n)
		{
			x = n;
			cout << "Constructor 2 Called" <<endl;
		}
};

int main() 
{
	CSample array1[2];
        //array1数组中2个元素,每个元素都是CSample对象,2个对象使用无参构造函数初始化
        //即无参构造函数会被调用2次,输出2行"Constructor 1 Called"
        cout << "step1" <<endl;
	CSample array2[2] = {4,5};
        //array2数组同样2个对象,但是这里已经给出了参数,
        //下标为0的对象用一个构造函数初始化,构造参数的参数是4
        //下标为1的对象用一个构造函数初始化,构造参数的参数是5        //这两个元素用有参构造函数初始化,输出2行"Constructor 2 Called"
	cout << "step2" <<endl;
	CSample array3[2] = {3};
        //array3也有两个对象,array3[0]有参构造函数初始化,array3[1]无参构造函数初始化
        //输出"Constructor 2 Called""Constructor 1 Called"

	cout << "step3" <<endl;
	CSample * array4 = new CSample[2];
        //动态分配了2个数组,这里对参数并没有做任何交代,使用无参构造函数初始化
        //输出2行"Constructor 1 Called"
	delete []array4;//new出来的对象,需要使用delete将其空间收回

	return 0;

}
输出:
Constructor 1 Called
Constructor 1 Calledstep1
Constructor 2 Called
Constructor 2 Called
step2
Constructor 2 Called
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called

#include <iostream>
using namespace std;

class Test
{
	public:
		Test(int n)
		{

		}//(1)
		Test(int n,int m)
		{

		}//(2)
		Test()
		{

		}//(3)
};

Test array1[3] = {1,Test(1,2)};
//三个元素分别用(1), (2), (3)初始化 

Test array2[3] = {Test(2,3),Test(1,2),1};
//三个元素分别用(2), (2), (1)初始化 

Test *pArray[3] = {new Test(4),new Test(1,2)};
//三个元素分别用(1), (2)初始化
//pArray是一个指针数组,而不是对象数组,
//其中每一个元素都为指针,指针不用初始化,这里对指针数组的前两个元素进行初始化,
//new Test(4)返回值的类型是Test *,用new出来对象的地址去初始化数组中的元素,
//数组前两个元素分别各自指向一个new出来的对象,这条语句生成2个对象,而不是3个,
//因为pArray[2]是一个未经初始化的指针,也不知道指向哪里,pArray[2]的生成并不会导致任何对象的生成

int main() 
{
	return 0;
}

复制构造函数(copy constructor)

  • 只有一个参数,即同类对象的引用。

  • 形如X::X(X&)或X::X(const X &),二者选一后者能以常量对象作为参数

  • 如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。

    #include using namespace std;

    class Complex { private: double real,imag; }; Complex c1;//调用缺省无参构造函数,无参构造函数不一定存在,没写的话,编译器自动生成 Complex c2(c1);//调用缺省的复制构造函数,将c2初始化成和c1一样,c2变成c1的复制品

    //这里并没有写复制构造函数,这里是编译器自动生成的缺省的复制构造函数

    int main() { return 0; }

如果定义自己的复制构造函数,则默认的复制构造函数不存在。

#include <iostream>
using namespace std;

class Complex
{
	public:
		double real,imag;
	Complex()
	{

	}
	Complex(const Complex&c)
	{
		real = c.real;
		imag = c.imag;
		cout<<"Copy Constructor called";	
	} 
};

Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出Copy Constructor called

int main() 
{
	return 0;
}
  • 不允许有形如X::X(X)的构造函数(复制构造函数的参数必须为引用,不能为对象)

    #include using namespace std;

    class Complex { CSample(CSample c) {

    }//错,不允许这样的构造函数 
    

    };

    //Complex c1; //Complex c2(c1);

    int main() { return 0; }

复制构造函数起作用的三种情况

  • 当用一个对象去初始化同类的另一个对象时。

    Complex c2(c1); Complex c2 = c1;//初始化语句,非赋值语句,用复制构造函数去初始化c2,等价于上一句

  • 如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数将被调用。

    class A { public: A() { }; A(A&a)//复制构造函数 { cout<<"Copy constructor called"<<endl; } };

  • 如果函数的返回值是类A的对象是,则函数返回时,A的复制构造函数被调用:

    #include
    using namespace std; class A
    {
    public:
    int v; A(int n) { v = n; };

    A(const A & a)
    {
    	v = a.v;//把被初始化的v变成和参数v一样
    	cout << "Copy constructor called"<<endl;
    } 
    

    };

    A Func() { A b(4); return b;//复制构造函数的参数为b }; int main() { cout<<Func().v<<endl; //Func函数的返回值是个对象(A),这个对象是用复制构造函数初始化的, return 0; } 输出结果: Copy constructor called 4

复制构造函数被调用的三种情况:

1、用一个对象去初始化另外一个对象

2、函数的形参是个对象

3、函数的返回值是个对象

注意:对象间赋值并不导致复制构造函数被调用

#include<iostream>  
using namespace std;  

class CMyclass  
{  
public:  
	int n;
    CMyclass()
	{

	};
	CMyclass(CMyclass &c)//复制构造函数
	{
		n = 2*c.n;//被初始化对象n是参数的两倍
	}
}; 

int main()
{
	CMyclass c1,c2;
	c1.n = 5;
	c2 = c1;//赋值语句,不是初始化语句不会调用复制构造函数
	CMyclass c3(c1);//调用复制构造函数,c3中的n是c1的两倍

	cout<<"c2.n = "<<c2.n<<",";
        cout<<"c3.n = "<<c3.n<<endl;

    return 0;
}
输出:c2.n = 5,c3.n = 10

常量引用参数的引用

void fun(CMyclass obj_)
{
    cout <<"fun"<<endl;    
}
  • 这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大

  • 所以可以考虑使用CMyclass&引用类型作为参数

  • 如果希望确保实参的值在函数中不应该被改变,那么可以加上const关键字

    void fun(const CMyclass & obj) { //函数中任何试图改变obj值的语句都将是变成非法 }

类型转换构造函数

  • 定义类型转换构造函数的目的是实现类型的自动转换

  • 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数

  • 当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或者临时变量)。

    #include
    using namespace std;

    class Complex
    {
    public:
    double real,imag; Complex(int i)//类型转换构造函数 { cout<<"IntConstructor called"<<endl; real = i; imag = 0; }; Complex(double r,double i) { real = r; imag = i; } };

    int main() { Complex c1(7,8); Complex c1 = 12;//实部为12,虚部为0,初始化语句, c1 = 9;//9被自动转换成一个临时的Complex对象,再将对象的值赋给c1

    cout<<c1.real<<","<<c1.imag<<endl;//9,0
    
    return 0;
    

    }

构造函数在对象初始化时起作用,析构函数在对象消亡时起作用。

析构函数destructors

  • 名字与类名相同,在前面加‘~’,没有参数和返回值,一个类最多只能有一个析构函数

  • 析构函数对象消亡时即被自动调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的内存空间等

  • 如果定义类时没写析构函数,则编译器生成缺省析构函数。一般来说,可以认为缺省析构函数什么也不做

  • 如果定义了析构函数,则编译器不生成缺省析构函数

    #include using namespace std;

    class String { private: char *p; public: String() { p = new char[10]; } ~String(); };

    String::~String() { delete [] p;//在析构函数中delete掉动态分配的指针 };

    int main() {

    return 0;
    

    }

析构函数和数组

只要有对象消亡,就有析构函数被调用

对象数组生命周期结束时,对象会消亡,对象数组的每个元素的析构函数都会被调用。

#include <iostream>
using namespace std;

class Ctest
{
    public:
    	~Ctest()
		{
			cout<<"destructor called"<<endl;
		}	
};

int main()
{
    Ctest array[2];//2个对象消亡,引发析构函数被调用,故2次输出"destructor called"
    cout<<"End Main"<<endl;
    return 0;
}
输出:
End Main
destructor called
destructor called

析构函数和运算符delete

delete运算导致析构函数调用

Ctest * pTest;
pTest = new Ctest;//构造函数调用
delete pTest;//对象消亡,引发析构函数调用     //不delete不消亡,将new出来的对象Ctest消亡掉
--------------------------------------------------------------------
pTest  = new Ctest[3];//对象数组有3个对象,构造函数调用3次
delete [] pTest;//析构函数调用3次

若new一个对象数组,那么用delete释放时应该写[]。否则只delete一个对象(调用一次析构函数)

析构函数在对象作为函数返回值后返回后被调用

#include <iostream>
using namespace std;

class CMyclass
{
    public:
    	~CMyclass()
		{
			cout<<"destructor"<<endl;
		}	
};
CMyclass obj;//全局对象 
CMyclass fun(CMyclass sobj)//形参对象sobj
{
	//参数对象消亡也会导致析构函数被调用

	return sobj;//函数调用返回时生成临时对象返回 
}

int main()
{
    obj = fun(obj);
	//函数调用的返回值(临时对象)被用过后,该临时对象析构函数被调用 

    return 0;
}
输出:
destructor
destructor
destructor

第一个destructor是函数的形参对象sobj消亡时,引发析构函数被调用

第二个destructor是临时对象sobj引发的

第三个destructor是整个程序结束的时候,全局对象sobj消亡引发的

构造函数和析构函数什么时候被调用?

#include <iostream>
using namespace std;

class Demo
{
	int id;
    public:
    	Demo(int i)
		{
			id = i;
			cout << "id=" << id << "constructed" <<endl;
                        //变量生成会输出constructed
		}
		~Demo()
		{
			cout << "id=" <<id<<" destructed"<<endl;
                        //变量消亡会输出destructed
		}	
};

Demo d1(1);//全局对象初始化引发构造函数的调用     输出id = 1constructed 
void Func()
{
	static Demo d2(2);//静态局部变量,会等到整个程序结束的时候,对象消亡 id=2 constructed 
	Demo d3(3);//非静态局部对象 id=3 constructed 
	cout<<"func"<<endl;
}

int main()
{
	Demo d4(4);//输出 id = 4 constructed 
	d4 = 6;// 有类型转换构造函数地存在,
	cout << "main" << endl;
	{
		Demo d5(5);//局部对象, id=5 constructed 
                //局部变量的生存期,从程序走到定义它的这条语句开始直到包含它的最内层的那对大括号为止
	} //程序走到func时会引发d5的消亡
	Func();
	cout << "main ends" <<endl;
    return 0;
}
输出:
id=1constructed
id=4constructed
id=6constructed
id=6 destructed
main
id=5constructed
id=5 destructed
id=2constructed
id=3constructed
func
id=3 destructed
main ends
id=6 destructed
id=2 destructed
id=1 destructed

复制构造函数在不同编译器中的表现

#include <iostream>
using namespace std;

class A
{
    public:
    	int x; 
    	A(int x_):x(x_)//构造函数 
		{

			cout << x << "constructor called" <<endl;
		}
		A(const A&a)//复制构造函数 
		{
			//本例中dev需要此Const其他编译器不要 
			x = 2+a.x; 
			cout << "copy called" <<endl;
		}
		~A()
		{
			cout << x << "destructor called" << endl;
		}
};

A f()
{
	A b(10);
	return b;
}

int main()
{
	A a(1);
	a = f();
    return 0;
}

devC++对程序编译的时候做了一个优化,devC++会认为return b,返回一个对象给a,干嘛还要生成一个临时对象调用复制构造函数,多浪费时间,干脆把b return的值给了a,不就完事了嘛?这样就少生成一个对象,节省了时间,这就叫优化,一般情况下,这种优化不会有什么问题,一般情况下,复制构造函数也要执行复制的工作,不会像x = 2+a.x;这里一样变成加,不做赋值的工作,所以一般来说,这种优化是问题不大的。

总之,说明dev处于优化的目的并未生成返回临时对象。VS无此问题。

结构体和·类的一些区别:www.jianshu.com/p/efaef5ddc…