如果已经声明了一个类的对象如Student类
Student s1;
Student s2{s1};//以下三种方法均可定义与s1相同的对象s2
Student s2(s1);
Student s2=s1;//s1与s2有独立的内存空间;
如果没有自定义拷贝构造函数,编译器会提供默认的拷贝构造函数,那么,我们什么时候要自定义拷贝构造函数呢?
当默认的拷贝构造函数不能完全胜任对象的拷贝工作时。比如说,当我们使用new申请内存空间的时候,拷贝构造函数往往会出现错误。
定义的时候需要注意,拷贝构造函数的形参必须是类对象的引用,并且,最好是const引用。
Student(string = "",int = 8);
Student(const Student&);
//上面这一行即为自定义的拷贝构造函数的声明,
那什么时候会触发拷贝构造函数呢?
Student s1;
Student s2(s1);
Student s1;
avgScore(s1);
//这里avgScore函数中的形参为Student类的对象。
Student largerAge(Student s);
//这里函数的返回值是Student类的对象
s3 = s1.largerAge(s2);
本台插播:临时对象
这句是创建了临时对象,调用构造函数,当创建完成后,将值赋给s2之后会调用析构函数。
s2 = Student("KK",16);
当一个类中的成员是一个指针时,要在构造函数中使用new分配内存,如:
class Student
{
public:
Student(const string& = "", int = 19, double = 0, double = 0, double = 0);
~Student();
Student(const Student&);
double AvgScore();
private:
string strName; //描述学生的姓名
int nAge; //描述学生的年龄
double* pScore; //指针,用来存储3门课的考试成绩
};
Student::Student(const string& name, int age, double scrEng, double scrMath, double scrCpp)
{
strName = name;
nAge = age;
pScore = new double[3];
pScore[0] = scrEng;
pScore[1] = scrMath;
pScore[2] = scrCpp;
}
Student::~Student()
{
cout << "Student destructor is called." << endl;
delete[] pScore;
cout << "Student destructor is finished, return..." << endl;
}
Student::Student(const Student& s)//拷贝构造函数
{
strName = s.strName;
nAge = s.nAge;
pScore = new double[3];//为当前对象的pScore申请内存空间
pScore[0] = s.pScore[0];//新的内存空间中的值与原内存空间中的值相等
pScore[1] = s.pScore[1];
pScore[2] = s.pScore[2];
}
当一开始没有深拷贝时,两个对象的pScore指向的是同一个内存空间,所以在调用析构函数并且delete对象时,会出现错误(codeblocks中对其进行了优化,不报错)。 如果没有指针的话,浅拷贝即可,不要忘记赋值
如下图:
我们如果要使用类的一个对象时,创建一个对象即可
Student s1;
但是如果我们需要使用多个对象,我们可以通过创建对象数组的方式进行初始化
Student stuArr[10];
//这句创建了具有10个Student类型的对象数组
//并调用了10次默认构造函数
//如果我们想对其中的对象赋值,可以这样写:
Student arrStu[5] = {{"AA",18},{"JD",13},{"oo",80},{"ee",20},{"tt",9}}
//也可以只对其中一部分赋值,未赋值的对象则会调用无参的构造函数。
有关指针:
point (*p)[3];
//这里point是类名,p是指向对象数组的指针
//因为这里的p和*是结合在一起的,是一个指针
point* p[3];
//这里point是类名,p是一个指针数组(有三个元素,每个元素是point对象指针)
//因为[]的优先级大于*的优先级!!!!!
两种方法:
string name;
int age;
//方法 Ⅰ
Student *p[3];//这里的p是指针数组,是一个数组
for(int i=0;i<3;i++){
cin>>name>>age;
p[i] = new Student(name,age);//p[i]是指针
p[i]->AvgScore();
}
//方法 Ⅱ
Student *p = new Student[3];//这里的p是指针
for(int i=0;i<3;i++){
cin>>name>>age;
p[i] = Student(name,age);//这里的p[i]是数组中的元素,即对象。
p[i].AvgScore();
}
对象作为函数的形参:
float computeSum(Student s);
//值传递,并且调用拷贝构造函数。
float computeSum(Student &s);
//引用传递。
方法:通过静态(static,即在内存中只有一份)数据成员对其定义,用static声明的数据成员可以被类的其他对象共享!
private:
static string tName;
string Student::tName = "t1";
//类内函数:
static string getTName(){
return tName;
}
//类外调用:
cout<<Student::getTName<<endl;
要注意,const类型的数据只能通过初始化表的形式,或在定义时进行赋值,不能在函数体中赋值!!!!
class Circle
{
public:
Circle(double r);
double Area(){return PI * dRadius * dRadius;}
double GetRadius(){return dRadius;}
private:
double dRadius;
const double PI; //或者在这里进行赋值
};
Circle::Circle(double r):PI(3.14)
{
//PI = 3.14; 这一行代码错误,常数据成员只能通过列表初始化的方式赋值,不能在函数体中赋值
dRadius = r;
}
静态常数据成员(数据被所有成员共享,并且值不允许变化):
static const double PI;
那么如何初始化呢?
只要是static类型的变量,要在类外初始化(这里整型和枚举类型可以在类内定义时赋初始化)!!!!!
常成员函数
用于保护对象的数据成员不被修改,如果不希望在成员函数中改变数据成员的值,最好把该函数定义为常成员函数,这样能提高程序的质量!!!!!good rabbit!!
注意:
const成员函数的格式,const只能写在参数括号的后面!!!!看下面例子中的const:
class Circle
{
public:
Circle(double r);
double Area()const{return PI * dRadius * dRadius;}//这里的const
double GetRadius()const{return dRadius;}//这里的const
void SetRadius(double r){dRadius = r;}
private:
double dRadius;
const double PI;
};
int main()
{
const Circle unit(1);
cout << unit.Area() << endl;//这里的调用是可以的,因为 Area()是常成员函数!!
Circle c(3);//这里的c不是常对象
cout << c.GetRadius() << c.Area() << endl;//c可以调用常成员函数和其他的成员函数
c.SetRadius(3.5);
cout << c.GetRadius() << c.Area() << endl;
//unit.SetRadius(2); //语法错误!!!!!!
return 0;
}
注:
Supplement:
友元机制可以让非类的成员函数访问类的私有数据成员
可以是类的非成员函数,也可以是另一个类的成员函数
1.如果友元是非成员函数:
声明形式:
在类内声明
class Student
{
public:
Student(const string& = "", double = 0, double = 0);
friend void ShowInfo(array<Student, 5>&); //类内声明友元函数
friend void SortStu(array<Student, 5>&);//类内声明友元函数
private:
string strName;
double dEng, dMath;
double dTot;
};
void ShowInfo(array<Student, 5>& stu)//因为友元函数不是类的成员,所以在定义时不需要写Student::ShowInfo()
{
cout << "姓名 英语 数学 总分" << endl;
for(auto x : stu)//这个是基于范围的for循环,每一个成员都是stu中的元素,进行循环
{
cout << setw(8) << left << x.strName;
cout << setw(5) << x.dEng << setw(4) << x.dMath << x.dTot << endl;
}
}
supplement:
array<类名,数量>
array<int,10>;
如果不为元素指定初值,那么创建的这个array,将用0填满,下面的data即为数组名。
array<int, 10> data{};
注:友元函数不是类的成员,一定是非成员函数。
在main函数调用的时候 函数名(参数) 即可
int main(){
array<Student,5> arrStu{Student{"Jason",75,82},Student{"KK",99,64},Student{"io",15,64},Student{"kd",89,74},Student{"ki",48,76}}
ShowInfo(arrStu);//友元函数的调用
SortStu(arrStu);//友元函数的调用
return 0;
}
2.如果友元是另一个类的成员函数
形式:
class Student; //提前声明类Student,如果Teacher类中需要使用Student类
class Teacher
{
public:
void StaMax(array<Student, 5>&, Student&, Student&, Student&);
private:
string strName; //教师的姓名
};
class Student
{
public:
Student(const string& = "", double = 0, double = 0);
friend void ShowInfo(array<Student, 5>&);
//下面即为Teacher类中的成员函数,作为Student类中的友元函数。
//声明友元函数,因为是Teacher类的成员函数,注意这里的格式。
friend void Teacher::StaMax(array<Student, 5>&, Student&, Student&, Student&);
void PrintStu();
private:
string strName;
double dEng, dMath;
double dTot;
};
3.友元类
格式:
class B{
...
friend class A;
...
}