C++ 学习笔记05

一、拷贝构造函数


思考:下面代码输出几次 “student”

class Student
{
public:
    Student()
    {
        cout<<"student"<<endl;
    }
private:
    string m_name;
};
int main()
{
    Student stu1;//创建对象调用构造函数
    Student stu2 = stu1;//使用同类型一个对象初始化另一个对象,这就是复制对象,会调用拷贝构造函数
    Student stu3(stu1);//使用同类型一个对象初始化另一个对象,这就是复制对象,会调用拷贝构造函数
}

1.声明

函数名(类名)(const 类名 &对象名)
使用一个对象初始化另一个对象的时候调用拷贝构造
拷贝构造函数的参数列表必须是 const 类名 &对象名。

示例1

#include <iostream>
using namespace std;
class Person
{
private:
    int age;
public:
    Person(int age)
    {
        this->age = age;
    }
//根据参数列表判断这是拷贝构造函数,Person类型的拷贝构造函数,参数必须是Perosn类型
    Person(const Person& other)
    {
        cout<<"拷贝构造"<<endl;
    }
    void show()
    {
        cout<<this->age<<endl;
    }
};
int main()
{
    Person a(19);
    cout<<"start"<<endl;
    Person b(a);//调用对象b 的拷贝构造函数
    Person c = a;
    cout<<"end"<<endl;
    b.show();
}

2.默认拷贝构造函数

默认拷贝构造函数:

  1. 在没有显式的定义拷贝构造函数时,系统会自动生成一个拷贝构造函数,
    功能: 将成员变量逐个赋值(浅拷贝)
  2. 如果显式的定义拷贝构造函数,那么默认拷贝构造函数和默认构造函数都没有了

示例2

#include <iostream>
using namespace std;
class Person
{
private:
    int age;
public:
    Person(int age):age(age)
    {

    }
//Person类中没有显式的定义拷贝构造函数
    int getAge()
    {
        return age;
    }
};
int main()
{
    Person* p = new Person(19);//在堆空间创建一个对象
    Person* p2 = new Person(*p);//*p是一个Person对象,这里是使用同类型的一个对象初始化另一个对象,调用拷贝构造函数,但是在Person中没有显式的定义拷贝构造函数,所以这里调用默认拷贝构造函数
    cout<<p->getAge()<<endl;,
    cout<<p2->getAge()<<endl;
}

总结

  1. 如果没有自定义的拷贝构造函数则系统自动生成一个默认的拷贝构造函数
  2. 当采用直接初始化或复制初始化实例化对象时,系统自动调用拷贝构造函数
    Person p;
    Person p2 = p;
    Perosn p3(p);
  3. 系统默认拷贝构造函数的功能:逐个对成员变量进行赋值
  4. 显示的定义了拷贝构造函数,系统默认的就不存在了,函数的功能(对变量进行的赋值),就由我们自己来完成了

3.拷贝构造函数调用三种情况:

(1)第一种

程序中需要创建一个新对象 并用另一个同类的对象对它初始化

示例3

#include<iostream>
using namespace std;
class Person
{
public:
    int age;
    string name;
    Person(int age)
    {
        this->age = age;
        cout<<"构造函数"<<endl;
    }
    Person()
    {
        cout<<"无参构造"<<endl;
    }
    Person(const Person& other)
    {
        age = other.age;
        name = other.name;
        cout<<"拷贝构造函数"<<endl;
    }
    void show()
    {
        cout<<age<<endl;
    }
};
int main()
{
    Person* p = new Person(12);//构造函数
    Person * p1 = new Person(*p);//调用拷贝构造
    p1->show();//12

    Person a(2);//构造函数
    Person b = a;//调用拷贝构造
    b.show();//2
    Person c(a);//调用拷贝构造
}

(2)第二种

当函数的参数为类的对象时

示例4

void farsight(Person p); 
int main()
{
    Person a(19);
    cout<<"start"<<endl;
    farsight(a);//调用拷贝构造
//因为在调用farsight函数时,需要创建形参对象p,形参p需要实参a对它初始化
    cout<<"end"<<endl;
}
void farsight(Person p)
{
cout<<”farsight call”<<endl;
}

示例5

void farsight(Person p); 
int main()
{
    //Person a(19);
    cout<<"start"<<endl;
    farsight(Person(19));//因为匿名对象生命周期短暂,所以形参直接使用实参的匿名对象
    cout<<"end"<<endl;
}
void farsight(Person p)
{
    cout<<"farsight call"<<endl;
}

示例6

void farsight(Person& p);
int main()
{
    Person a(19);
    cout<<"start"<<endl;
    farsight(a);//这里调用函数没有创建新对象,所以不会调用拷贝构造
    cout<<"end"<<endl;
}
void farsight(Person& p)
{
}

(3)第三种

函数的返回值为类对象,如果对象生命周期大于函数,会创建一个新的对象,促发拷贝;如果函数结束对象就被删除,不会创建新的临时对象,不会促发拷贝。

示例7

Person farsight();
int main()
{   
    Person p=farsight();
    cout<<&p<<endl;
}

Person farsight()
{
    Person* p = new Person(19);
    cout<<p<<" after new"<<endl;
    return *p;
    /*
    Person p;
    cout<<&p<<endl;
    return p;*/
}

练习1

分文件写
1、Student类
1.私有成员score,name
2.构造函数传入score,name
3.公有函数setScore给score赋值
4.公有函数show显示成绩和名字

2、Teacher类
1.Student *students[3];私有数组保存学生对象
2.公有构造函数,给students数组赋值 创建三个学生对象
3.公有函数 Student * randHappyBoy()随机抽取一个学生指针
4.公有函数hehe(Student*)将随机抽取的这个学生改为0分
5.公有函数teacherShow()显示最终的所有学生成绩 三个学生的成绩
cstdlib ctime srand(time(0)) rand()%3

5个文件

Student.h

查看Student.h代码

#ifndef STUDENT_H
#define STUDENT_H
#include <string>
using namespace std;
class Student
{
private:
    int score;
    string name;
public:
    Student(int score, string name);
    void show();
    void setScore(int score);
};
#endif // STUDENT_H

Student.cpp

查看Student.cpp代码

#include "Student.h"
#include <iostream>
Student::Student(int score, string name):
    score(score),name(name)
{

}
void Student::show()
{
    cout<<name<<" "<<score<<endl;
}
void Student::setScore(int score)
{
    this->score = score;
}

Teacher.h

查看Teacher.h代码

#ifndef TEACHER_H
#define TEACHER_H
#include "Student.h"
#include <cstdlib>
#include <ctime>
class Teacher
{
private:
    Student* students[3];
public:
    Teacher();
    Student randHappyBoy();//这里返回Student不能完成功能,因为产生了拷贝的对象,不是对数组里的对象修改
    void hehe(Student* hb);
    void teacherShow();
};
#endif // TEACHER_H

Teacher.cpp

查看Teacher.cpp代码

#include "Teacher.h"
Teacher::Teacher()
{
    students[0] = new Student(19, "小明");
    students[1] = new Student(90, "小红");
    students[2] = new Student(82, "小绿");
}
Student Teacher::randHappyBoy()
{
    return *students[rand()%3];
}
void Teacher::hehe(Student *hb)
{
    hb->setScore(0);
}
void Teacher::teacherShow()
{
    for(int i = 0;i < 3;i++)
    {
        students[i]->show();
    }
}

main.cpp

查看main.cpp代码

#include <iostream>
#include "Teacher.h"
#include "Student.h"
using namespace std;
int main()
{
    srand(time(0));
    Teacher t;
    Student hb = t.randHappyBoy();
    hb.show();
    t.hehe(&hb);
    t.teacherShow();
    return 0;
}

二、私有构造函数和拷贝构造函数

示例8

私有构造函数 不能在类的外部创建对象

#include <iostream>
using namespace std;
class Person
{
private:
    int age;
private:
    Person(int age)
    {
        this->age = age;
        cout<<"构造"<<endl;
    }
public:
Person()
{}
public:
    Person(const Person& other):age(other.age)
    {
        cout<<"拷贝构造"<<endl;
    }
    void show()
    {
        cout<<this->age<<endl;
    }
};
int main()
{
    Person p(19);//报错  因为匹配到的构造函数是私有的
Person p2;//不报错,因为去匹配的构造函数是公有的
}

示例9

私有拷贝构造函数 不能在类的外部复制对象

#include <iostream>
using namespace std;
class Person
{
private:
    int age;
public:
    Person(int age)
    {
        this->age = age;
        cout<<"构造"<<endl;
    }
private:
    Person(const Person& other):age(other.age)
    {
        cout<<"拷贝构造"<<endl;
    }
    void show()
    {
        cout<<this->age<<endl;
    }
};
int main()
{
    Person p(19);
    Person b = p;//报错 不能复制对象
}

三、深拷贝与浅拷贝

1.浅拷贝

拷贝对象的时候,不对指针成员所指向的对象进行拷贝,两个对象的指针成员指向的是同一个对象

示例10

class Phone
{
private:
    string name;
public:
    Phone(string name):name(name){}
};

class Person
{
private:
    Phone* pPhone;//当一个对象里有指针类型成员时,才有必要去考虑深拷贝和浅拷贝
public:
    Person()
    {
        pPhone = new Phone("诺基亚plus");
    }
    Phone* getPhone()
    {
        return pPhone;
    }
};

int main()
{
    Person* xiaoming = new Person();
    Person xiaoqiang(*xiaoming);//Person中没有显式的定义拷贝构造函数,所以使用默认拷贝构造函数,默认拷贝构造函数的逻辑是浅拷贝
    cout<<xiaoming->getPhone()<<endl;//这两个对象的phone指针应该指向同一个对象
    cout<<xiaoqiang.getPhone()<<endl;
}

2.深拷贝

需要在对象的拷贝构造函数中对成员指针所指向的对象也进行拷贝

示例11

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Phone
{
private:
    string name;
public:
    Phone(string name):name(name){}
    string getName()
    {
        return name;
    }
};
class Person
{
private:
    Phone* pPhone;
public:
    Person()
    {
        pPhone = new Phone("诺基亚plus");
    }
    Person(const Person& other)//Person& other = *xiaoming;
    {
        pPhone = new Phone(*(other.pPhone));//使用other的phtoe对象拷贝了一个对象
        //这样,当前对象里的phone和other不是一个对象
    }
    Phone* getPhone()
    {
        return pPhone;
    }
};
int main()
{
    Person* xiaoming = new Person();
    Person xiaoqiang(*xiaoming);
    cout<<xiaoming->getPhone()<<endl;
    cout<<xiaoqiang.getPhone()<<endl;
    cout<<xiaoming->getPhone()->getName()<<endl;
    cout<<xiaoqiang.getPhone()->getName()<<endl;
}

注意:
能用浅拷贝就不用深拷贝
当类中有指针类型的成员时,才去考虑深拷贝和浅拷贝的问题。
系统默认的拷贝构造函数是: 浅拷贝

四、类成员的初始化顺序

先按照成员变量的声明顺序初始化成员变量,最后调用构造函数

示例12

#include <iostream>
using namespace std;
class A
{
public:
    A()
    {
        cout<<"A"<<endl;
    }
};
class B
{
public:
    B()
    {
        cout<<"B"<<endl;
    }
};
class C
{
private:
    B b;
    A a;    
public:
    C()
    {
        cout<<"C"<<endl;
    }
};
int main()
{
    C c;//B A C
}
End

本文标题:C++ 学习笔记05

本文链接:https://www.chisato.cn/index.php/archives/142/

除非另有说明,本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

声明:转载请注明文章来源。

最后修改:2021 年 10 月 09 日 02 : 07 PM
如果觉得我的文章对你有用,请随意赞赏