C++(拷贝构造函数、对象数组(指针)、static、const、友元)

拷贝构造函数

如果已经声明了一个类的对象如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中对其进行了优化,不报错)。 如果没有指针的话,浅拷贝即可,不要忘记赋值
如下图:

C++(拷贝构造函数、对象数组(指针)、static、const、友元)_第1张图片
综上,当类中包含指针变量的数据成员的时候:

  • 在构造函数中new,申请动态内存空间
  • 在析构函数中,delete申请的动态内存空间
  • 编写拷贝构造函数,实现对指针成员的深拷贝!!!


对象数组

我们如果要使用类的一个对象时,创建一个对象即可

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,即在内存中只有一份)数据成员对其定义,用static声明的数据成员可以被类的其他对象共享!

  1. 在类内声明
privatestatic string tName;
  1. 在类外初始化,不需要static
string Student::tName = "t1";
  1. 静态数据成员的类外访问
//类内函数:
static string getTName(){
	return tName;
}

//类外调用:
cout<<Student::getTName<<endl;
  1. 因为tName是私有成员,所以类外不能直接访问,只能在类中加入函数进行访问,如果是static类型的函数,那么函数体中只能出现static类型的成员。


const

  1. 用于声明类中的数据成员或成员函数,目的是保护类中的数据不被更改。

要注意,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; 
};
  • const对象只能调用const成员函数!!!!
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;
}


注:

  • 静态成员函数没有this指针!!!!
  • 静态成员函数内部不能访问同类的非静态成员变量,也不能调用同类的非静态成员函数!!!!
  • 静态不能访问非静态,但非静态成员函数可以访问静态成员变量
  • 要分清const和static的性质!!!!
  • 在没有任何对象存在的情况下也可以访问类的静态成员!!!!

Supplement:

  • 全局变量和静态变量存储在全局数据区,不会因对象的产生和消亡有任何影响
  • 局部变量,函数参数和返回值存储在栈中,随着对象的产生和消亡自动分配和释放内存
  • 动态创建的数据存储在堆中,需要通过new和delete来申请和释放内存


友元

友元机制可以让非类的成员函数访问类的私有数据成员
可以是类的非成员函数,也可以是另一个类的成员函数

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:

  • STL从根本上说,是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。(知道即可)
  • Array的用法: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;
	...
}
  • 这里A是B的友元类,但B不一定是A的友元类!!!!!
  • 并且友元关系不具有传递性
  • 友元在一定的程度上会破坏程序的封装性,除非能极大的提高程序效率,一般情况下不使用友元
  • 试一下三种方法:
    C++(拷贝构造函数、对象数组(指针)、static、const、友元)_第2张图片


放J.Cole靓照一张
C++(拷贝构造函数、对象数组(指针)、static、const、友元)_第3张图片

Over

GL&HF

你可能感兴趣的:(C++)