c++的构造函数

目录

构造函数

1.构造函数: 

2.构造函数的特点: 

默认构造函数 -- 没有参数的构造函数

1. 合成(自动)的默认构造函数(一般不常用) 

1) 介绍,以及为什么不使用

 2)可以使用合成默认构造函数的情况

2. 自定义的默认构造函数 

 1)介绍

 有参构造函数 -- 带参数的构造函数

 1. 自定义有参构造函数

 1) 简述

 2)  使用有参构造函数实例化对象

3)有参构造函数定义时需要注意:  

1. 使用的参数名字和类内的属性名字不同

2. 使用的构造函数参数的名字和类内的属性名字一样 

3. 解决办法:  使用this指针 :指向当前对象(可以理解为调用函数的这个对象)

拷贝构造函数 

1. 合成的拷贝构造函数(有很大风险) -- 浅拷贝

1)简述 -- 浅拷贝

 2)合成拷贝构造函数具有很大的风险(浅拷贝的风险)

2. 自定义的拷贝构造函数 -- 深拷贝

 1)简述 -- 深拷贝

2)使用深拷贝来解决浅拷贝的问题 

3)浅拷贝和深拷贝 

4)拷贝构造函数的调用时机 

返回过程: 

5) 拷贝构造函数注意事项

赋值构造函数

合成的赋值构造函数

1)简述

2) 代码

3)合成赋值构造函数也存在问题 -- 也是浅拷贝 

自定义的赋值构造函数 

1)含义 

 2)代码

3)赋值构造函数的写法 

总结: 

构造函数

构造函数是c++类中的概念,用来给对象的属性进行初始化和一些其它的操作(相当于创造一个东西时,赋予它一些特性)。 

1.构造函数: 

其实从字面意思来理解,就是创造一个东西的函数,对于类来说这个东西就是对象。 

所以,c++语法规定,在我们实例化一个类对象的时候,都会默认调用构造函数。

2.构造函数的特点: 

1) 创建对象时自己调用 

2)    没有返回值,函数名和类名一样

3)  可以利用函数的重载,写多个构造函数。 

 

3. 注意:

构造函数必须设置为公有,因为我们实例化对象在类外,所以构造函数必须能够被外界使用。  

默认构造函数 -- 没有参数的构造函数

1. 合成(自动)的默认构造函数(一般不常用) 

1) 介绍,以及为什么不使用

我们上面说了,c++的语法规则:实例化一个对象就会自动调用构造函数。 

但是如果我们没有在类中写构造函数呢?  那么编译器会默认给我们生成一个合成的默认构造函数。这个构造函数其实内部什么也没有,就是一个空函数。   那没什么要有它呢?-- 也许为了语法统一。

但是,使用合成的默认构造函数的风险很高。   看代码:

class Human {
public:
	int getAge();
	void visit();

private:
	int age;
};

int Human::getAge() {
	return this->age;
}

void Human::visit() {
	cout << getAge() << endl;
}

int main(void) {

	Human man; // 创建man对象

	man.visit(); // 使用visit函数来打印age的值

	system("pause");

	return 0;
}

c++的构造函数_第1张图片

 上面代码,我们创建了一个对象,并且我们没有在类中实现构造函数,这是编译器会自己创建一个构造函数(空实现)。

我们通过visit()打印通过对外接口getAge()获得的age的值。-- 看上面的结果,是一个很大的负值。这中情况很常见 -- 没有赋值直接使用。

为什么会是上面的结果呢?  

因为,对象man中的属性age,没有赋值,在实例化的时候,调用的也是编译器自己创建的默认构造函数,也是空实现,不会对age进行赋值,所以age在使用的时候是没有赋值的。所以会出现问题。

这就是为什么不使用它的原因了。 

 2)可以使用合成默认构造函数的情况

有一种情况,我们是可以使用默认构造函数的。

class Human {
public:
	int getAge();
	void visit();

private:
	int age = 18;  // 仅c++11之后可以
};

比如代码中,我们在类内定义的age变量,定义的时候已经初始化了。这时,即使再输出age也不会出现上面的情况(构造函数不赋值的情况下)。

c++的构造函数_第2张图片  正常输出。 

所以,当我们类内的数据在定义的时候全都已经初始化了(必须全部初始化,因为有不初始化的就存在风险),就可以使用合成默认构造函数了。 

注意: 只有c++11之后才支持在类内定义数据时,进行初始化。

2. 自定义的默认构造函数 

 1)介绍

上面说到,我们一般都不会使用合成默认构造函数,我们一般都会自己定义一个构造函数。 

在自定义的构造函数中我们就可以对相应的数据进行初始化了。 

class Human {
public:
	int getAge();
	void visit();
	Human();

private:
	int age;
};

int Human::getAge() {
	return this->age;
}

Human::Human() {
	age = 18;
}

void Human::visit() {
	cout << getAge() << endl;
}

int main(void) {

	Human man; // 创建man对象

	man.visit(); // 使用visit函数来打印age的值

	system("pause");

	return 0;
}

上面代码,就定义了一个默认构造函数,Human(),我们在实现构造函数中,将age属性进行了初始化。 

在自定义的构造函数中我们可以根据自己的需要进行设置,将相应的属性进行初始化。

注意:  当我们自己定义了构造函数的时候,编译器就不会给我们提供了。

 有参构造函数 -- 带参数的构造函数

 1. 自定义有参构造函数

 1) 简述

前面提到构造函数可以有多个,带参数的构造函数是我们自己写的。 前面写的默认构造函数没有参数的。

有参构造函数:就是带参数的构造函数,所以它的书写规则和无参构造很类似,就是带了个参数。 

class Human {
public:
	Human(int age, string name);

	int getAge();
	string getName();
	void visit();
private:
	int age;
	string name;
};

int Human::getAge() {
	return this->age;
}

string Human::getName() {
	return name;
}

void Human::visit(){
	cout << getAge() << endl;
	cout << getName() << endl;
}

Human::Human(int age, string name) {

	this->age = age; // 如果名字相同使用this指针,或者可以将参数的名字设置成不一样的
	this->name = name;
}

int main(void) {
	Human man1(18, "男神");

	man1.visit();

	system("pause");

	return 0;
}

代码中:定义了一个有参构造函数:Human(int age,string name) ;

 2)  使用有参构造函数实例化对象

代码中有参构造有两个参数,用来接收用户用来初始化属性的值。 

所以在使用有参构造函数创建对象的时候,需要传入参数。Human man1(18,"男神"); 就是调用有参构造函数进行创建对象。 

3)有参构造函数定义时需要注意:  

1. 使用的参数名字和类内的属性名字不同
class Human {
public:
	// 有参构造函数
	Human(int age1, string name1);
private:
	int age;
	string name;
};


Human::Human(int age1, string name1) {
	age = age1;
	name = name1;
}

当有参构造函数的参数名字和类内属性名,名字不一样的时候,可以直接进行赋值。 

2. 使用的构造函数参数的名字和类内的属性名字一样 
class Human {
public:
	// 有参构造函数
	Human(int age, string name);
private:
	int age;
	string name;
};


Human::Human(int age, string name) {
	age = age;  // 此时这两个age都表示的是参数,所以不正确。name类似
	name = name;
}

当参数的名字和属性名字相同的时候,就不能直接赋值了。 

age = age; // 此时这两个age都表示的是形参里面的age,并不是类内属性的age,所以不对。 

3. 解决办法:  使用this指针 :指向当前对象(可以理解为调用函数的这个对象)
Human::Human(int age, string name) {
	this->age = age; 
	this->name = name;
}

使用this之后,就可以了。 因为this指向当前对象,所以this->age就是指当前对象的age属性。

拷贝构造函数 

拷贝构造函数:在一个对象初始化的时候,给它赋值另外一个对象,这是就会默认调用拷贝构造函数。

Human man2 = man1; 

1. 合成的拷贝构造函数(有很大风险) -- 浅拷贝

1)简述 -- 浅拷贝

合成的拷贝构造函数就是:当自己没有实现拷贝构造函数的时候,编译器会自动生成一个合成拷贝构造函数。

 拷贝构造函数会将man1中属性的值拷贝到man2的属性中。

 2)合成拷贝构造函数具有很大的风险(浅拷贝的风险)

        浅拷贝:就是只将属性的值,拷贝到另外一个对象。

         这样看,浅拷贝好像没有什么问题。如果使用普通的变量,没有什么问题,但是如果属性中指针,那么就会出现问题。

举个例子: 

int age;
string name;
char *a;


man1 :  age=12,name="帅哥",*a = 'a'

Human man2 = man1;

将man1中的值拷贝到man2中, 我们没有定义拷贝构造函数,所以编译器自动生成,进行浅拷贝:  将man1 中 age 的值复制给 man2中 age,name也同理,

重点来看指针的拷贝:浅拷贝只是将指针变量a中存放的地址拷贝到man2的a中,也就是说进行浅拷贝之后,man1中的a和man2中的a指向同一片内存,同一块数据。 

可能会疑问问什么不能指向同一个呢? -- 很简单,man1 和 man2两个对象毫不相关,如果这个指针用来表示的是资产,那你说,两个人的资产怎么能放到一起呢。

所以这样用一定会出问题,会有很大的风险。 

2. 自定义的拷贝构造函数 -- 深拷贝

 1)简述 -- 深拷贝

前面说到,系统自动提供的拷贝构造函数只能进行浅拷贝,会造成很大的风险。 

那怎么办呢?--  那就自己实现一个呗,然后使用深拷贝来解决这个问题 

2)使用深拷贝来解决浅拷贝的问题 

 浅拷贝的问题是: 对指针进行拷贝时,不会开辟新空间,直接将新对象的指针指向别的对象的内存,这就会造成很大的风险。

问:那深拷贝如何解决这个问题呢? 

我们可以在自己实现的拷贝构造函数中,给新对象的指针开辟一片空间,来存放其他对象指针中的值 -- 这样就不会共用同一片空间了。

class Human {
public:
	Human();
	Human(const Human&); // 函数声明可以不写参数名称
private:
	int age;
	string name;
	char* f1;
};

Human::Human() {
	age = 20;
	name = "帅哥";

	f1 = new char[10];
	strcpy_s(f1,10,"好好学习");  // 使用字符串拷贝给字符串进行赋值
}

Human::Human(const Human& man) {
	age = man.age;
	name = man.name;

	f1 = new char[10];
	strcpy_s(f1, 10, man.f1);  // 将man中f1指向内存的值拷贝到此对象的f1中
}

int main(void) {
	Human man1;
	Human man2 = man1;   // 在对象初始化时赋值给另外一个对象,则会默认调用拷贝构造函数

	system("pause");

	return 0;
}

Human(const Human&); 就是我们自定义的拷贝构造函数声明。

在函数实现的过程中, 我们又为新对象的f1指针创建了一片内存,用来存放其它对象的值。

3)浅拷贝和深拷贝 

浅拷贝:系统自己生成的拷贝构造函数,只是将变量中的值浅浅的复制过去,不管你是不是有风险, 比如:对于指针变量,只是将指针变量存放的地址复制过去,使它指向了同一片空间。

深拷贝: 为了解决浅拷贝带来的风险,我们需要自己实现拷贝构造函数,为新对象的指针开辟空间,将别的对象指向的内容复制过去,而不是简单的将指针中存储的地址复制过去。

4)拷贝构造函数的调用时机 

1. 当函数参数不是引用(值传递),参数类型是我们定义的类的时候 

void test(Human man) {   // 传参数的过程就是 Human man = man1 这不就是拷贝构造
	// 测试语句
}

int main(void) {
	Human man1;

	test(man1);

	system("pause");

	return 0;
}

2. 返回值为我们定义的类 

Human test(Human& man) {
	// 测试语句

	return man;
}

int main(void) {
	Human man1;

	test(man1);

	system("pause");

	return 0;
}
返回过程: 

其实就是创建一片临时空间来存放man的值,然后返回给主调函数。  这个过程其实和Human man1 = man;是类似的,只是此处我们不知道变量名称。

3. 初始化对象时,直接使用另外一个对象初始化,使用=或者()都可以。都会自动调用拷贝构造函数。 

int main(void) {
	Human man1;
	Human man2 = man1;   // 在对象初始化时赋值给另外一个对象,则会默认调用拷贝构造函数

	Human man3(man1);

	system("pause");

	return 0;
}

4. 使用对象数组初始化的时候 

int main(void) {
	Human man1;
	Human man2 = man1;  

	Human man3(man1);

	Human man4[3] = { man1,man2,man3};

	system("pause");

	return 0;
}

其实就是定义了一个数组,数组中的每个元素都是Human类型,所以其实就相当于: 

Human man4[0] = man1;        Human man4[1] = man2;       Human man4[2] = man3;  自动调用拷贝构造函数。 

5) 拷贝构造函数注意事项

      1. 拷贝构造函数的参数类型必须是: const Human& man 这种 

      2.  拷贝构造函数的参数只能有一个,因为初始化的时候,只能一个对象作为右值。 

      3. 由于2.,所有拷贝构造函数只能有一个,因为它无法进行函数重载,因为它只能有一个参数,并且参数的类型和数量都是固定的。 (有参构造可以重载,因为参数个数个类型不受限制)

赋值构造函数

合成的赋值构造函数

1)简述

赋值构造函数和其他的构造函数不同(具体看实现)。  合成赋值构造函数也是系统自己提供

2) 代码
int main(void) {
	Human man1,man2;

	man2 = man1;  // 在不初始化时,进行赋值,调用赋值构造函数

	system("pause");

	return 0;
}

当我们使用=将一个对象赋值给另外一个对象的时候,系统会自动调用赋值构造函数。(注意:不是初始化的时候,初始化时调用的是拷贝构造函数) 

3)合成赋值构造函数也存在问题 -- 也是浅拷贝 

还是浅拷贝,当属性中有指针时,虽然两个对象的指针都指向各自的内存,但是合成赋值构造函数只是进行简单的赋值,对指针进行赋值时,只是将指针变量的值赋值给新对象的指针,这样又会导致两个对象的指针直系那个同一片内存。(浅拷贝)  

自定义的赋值构造函数 

1)含义 

和上面一样,既然浅拷贝有问题,那么我们就自定义一个赋值构造函数来实现深拷贝。 

 2)代码
class Human {
public:
	Human();

	Human& operator=(const Human& man);  // 赋值构造函数 -- =运算符重载
private:
	int age;
	string name;
	char* f1;
};

Human::Human() {
	age = 20;
	name = "帅哥";

	f1 = new char[10];
	strcpy_s(f1,10,"好好学习");  // 使用字符串拷贝给字符串进行赋值
}

Human& Human::operator=(const Human& man) {
	age = man.age;
	name = man.name;

	// 深拷贝
	strcpy_s(f1, 10, man.f1);
    
    return *this; // 返回此对象
}


int main(void) {
	Human man1,man2;

	man2 = man1;  // 在不初始化时,进行赋值,调用赋值构造函数

	system("pause");

	return 0;
}

问:为什么此处的深拷贝不需要开辟新空间? 

此处的深拷贝,已经不需要开辟空间了,因为我们在实例化对象的时候 ,已经在默认构造函数中,对每个对象的f1指针都开辟了单独的空间。所以,不需要再开辟空间了。

我们只需要将man1中的f1内存中的值拷贝到man2的f1指向的内存即可。(而不是,将f1中的地址赋值过去)。 

3)赋值构造函数的写法 

     1. 首先赋值构造函数就是一个=的重载函数 

     2. 参数传入的是作为=右值的对象,而调用函数的是=左值的对象。(this指针指向当前对象 ) 

     3.  返回Human&, 是用来返回调用函数的对象的。this指向调用函数的当前对象,*this就是此对象。

     4. 3.的原因:   为了能够实现连续赋值的情况  --  man  = man1  = man2;  

         man1 = ma2; 调用赋值构造函数,返回man1, 再进行 man = man1。 

总结: 

综合上面的看,构造函数其实就是,当我们对对象进行相应的操作时,系统会自动去调用的函数。为了构造当前对象,去调用函数。 

你可能感兴趣的:(c++,开发语言)