某些函数在调用时不需要显式提供函数名,编译器会自动调用,例如类构造函数与类析构函数。
构造函数:
与类同名的成员函数。创建类的一个实例时,编译器自动调用合适的构造函数。
一个类至少有一个构造函数,即可以对构造函数进行重载;每个构造函数的函数签名不同。例:
#include<iostream>
using namespace std;
class Person {
public:
//没有参数的默认构造函数
Person();
//参数类型为const string引用的带参构造函数
Person(const string& n);
//参数类型为C风格字符串:const char*的带参构造函数
Person(const char* n);
void setName(const string& n);
void setName(const char* n);
const string& getName()const;
private:
string name;
};
构造函数的使用:
int main() {
//默认构造函数
Person per;
//带参构造函数
Person IvS("Ivan Sutherland");
}
顾名思义,构造函数用于"构造",对数据成员进行初始化,并且负责其他一些对象创建时需要的事务。
数据成员的初始化顺序取决于它们在类中被声明的顺序,而与它们在成员初始化列表中出现的顺序无关
构造函数最大的特点:
1.函数名与类名相同
2.没有返回类型
构造函数与其它函数相同,可以进行赋值、判断、循环、调用等功能;可以在类内/类外定义;
对象数组与默认构造函数:
假设C是一个类,可以定义任意维数的C对象数组;若C拥有默认构造函数,数组中每个C对象都会调用默认构造函数。例如:
#pragma once
#include<iostream>
using namespace std;
unsigned n = 0;
class C {
public:
C() { cout << "C" << ++n << "\n"; }
};
C arr[10];
测试:
#include<iostream>
#include"ctest.h"
using namespace std;
int main(){
C();
};
输出结果:
构造函数约束对象的创建:
程序员将部分构造函数设计为私有,另一部分设计为公有;私有构造函数不能在类外调用,由类范围属性。
关于构造函数的自动生成:
编译器找不到任何构造函数时,会生成一个公有默认构造函数;
若一个类已声明构造函数,则不会生成公有默认构造函数;
一个类声明了一个非公有的默认构造函数,编译器不生成公有默认构造函数。
在带参构造函数中有两类比较重要:
拷贝构造函数:创建一个新的对象,此对象是另外一个对象的拷贝品;
转型构造函数:用于类型间的转换,只有一个参数,但是第一个之后的每个参数都必须有默认值;
拷贝构造函数:
拷贝构造函数的原型(必须是引用):
//正确:
Person(const Person&);
Person(Person&);
//以下错误:
//Person (Person);
//会提示:"'类Person'的复制构造函数不能带有'Pesron'类型的参数"
//多参数:
Person( const Person &p,bool married = false);
若类设计者不提供拷贝构造函数,编译器将自动生成。完成的操作:
将源对象所有数据成员的值逐一赋值给目标对象相应的数据成员
一个类包含指向动态存储空间指针类型的数据成员,就为这个类设计拷贝构造函数
例:
#pragma once
#include<iostream>
using namespace std;
class nameList {
public:
nameList() { size = 0; p = 0; }
nameList(const string[], int);
void set(const string&, int);
void set(const char*, int);
private:
int size;
string* p;
};
nameList::nameList(const string s[], int si) {
size = si;
p = new string[size];
for (int i = 0; i < size; i++) {
p[i] = s[i];
}
}
#include<iostream>
#include"ctest.h"
using namespace std;
int main() {
string list[] = { "A","B","C" };
nameList d1(list, 3);
d1.dump();
//A,B,C
nameList d2(d1);
d2.dump();
//A,B,C
d2.set("D", 1);
d2.dump();
//A,D,C
d1.dump();
//A,D,C
return 0;
}
上例没有为类nameList定义拷贝构造函数,则定义d2时将会调用编译器提供的拷贝构造函数,将d1的数据成员拷贝到d2;此时指针d1.p和d2.p指向同一块储存空间:数组的第一项的地址。
可能引发危险:操作一个可能会改变另一个的内容。
为避免错误,为nameList类提供一个满足要求的拷贝构造函数。
禁止对象拷贝:有些对象较大,进行对象的拷贝操作(例如采用传值方式将对象传递给一个函数或者返回一个对象时),浪费资源。此时通过将拷贝构造函数设计成私有成员来禁止对象间的拷贝操作。
关于对象拷贝:
转型构造函数:
一个单参数的构造函数;将一个对象从一种数据类型(由参数指出)转换为另一种数据类型(该构造函数所属的类)
假设函数f的参数类型为Person对象,以一个string作为参数来调用f;
//f的参数类型为Person对象
void f(Person p);
//以一个string类型作为参数来调用f
string s = "STRING";
f(s);
需要Person类拥有一个将string转型成Person的转型构造函数,编译器就可以在string对象s上调用这个转型构造函数,构造一个Person对象做f的参数。
转型构造函数与隐式类型转换:
例中的Person类的转型构造函数支持隐式类型转换。
隐式类型转换方便但是可能有细微的问题;可以用关键字explicit关闭系统的隐式类型转换功能。
#include<iostream>
using namespace std;
class Person {
public:
//标记为explicit
explicit Person(const string& n) { name = n; }
//函数f的形参类型:Person
void f(Person s);
private:
string name;
};
int main() {
Person p("per1");
f(p);
//OK; p is a Person
//使用转型构造函数
string b = "per2";
f(b);
//ERROR; b is a string
//explicit关键字关闭隐式类型转换
}
构造函数的初始化:
对const类型的数据成员不能直接赋值,需要为构造函数添加一个初始化列表
class C {
public:
C() :c(0){x = -1;}
private:
const int c;
int x;
};
构造函数初始化程序:
初始化段:冒号+要进行初始化的数据成员+初始值
C() :c(0){x = -1;}
初始化列表的有效范围:
构造函数中,构造函数可以初始化const或者非const数据成员;const数据成员只能用初始化列表初始化。
class C {
public:
C() :c(0),x(-1){}
private:
const int c;
int x;
};
构造函数与操作符new和new[]:
使用动态方式为一个对象分配存储空间,C++操作符new和new[]飞陪存储空间并且调用相应的构造函数。
析构函数:
类比于对象的创建,在摧毁对象时,会自动调用一个析构函数。(对象的摧毁:以某个类作为数据类型的变量超出其作用范围;用delete操作符删除动态分配的对象)
特点:
1.是一个成员函数
2.(类C) 析构函数的原型:~C();
3.析构函数不带参数,不能被重载,所以每个类只有一个析构函数。
4.析构函数没有返回类型
创建对象时类构造函数完成初始化和其他相关操作;摧毁对象时析构函数完成清理工作(例如将构造函数分配的资源释放).
————————————2021-11-05-19:17————————————