C++ 面向对象基础(上)(嵌入式学习)

C++ 面向对象基础(上)

  • 1. 类与对象
    • 1.1 类的概念
      • JY
    • 1.2 类的实例化
      • JY
      • 练习1
      • 练习2
  • 2. 封装
    • 概念
    • 练习
  • 3.构造函数
    • 3.1 概念
      • JY
    • 3.2 有参构造函数
      • JY
    • 3.3 构造函数支持默认值
      • JY
    • 3.4 构造函数支持函数重载
      • JY
    • 3.5 构造初始化列表
      • JY
    • 3.6 拷贝构造函数
      • JY
    • 3.7 拷贝构造函数调用的场景
    • 3.8 深拷贝与浅拷贝
      • JY
        • 浅拷贝
        • 深拷贝
        • 深拷贝与浅拷贝例题
  • 4. 析构函数
    • JY
  • 5. 名字空间
    • 概念及运用
    • JY
      • 作用
      • 命名空间的使用

1. 类与对象

1.1 类的概念

在面向对象编程(Object-Oriented Programming,OOP)中,类是一种抽象数据类型,用于定义对象的属性(数据成员)和行为(成员函数)。类是对象的蓝图或模板,用于创建具体的对象。

类具有以下几个重要的概念和特点:

  1. 对象(Object):对象是类的实例化,是具体的、实际存在的实体。对象具有类定义的属性和行为,并可以通过调用类的成员函数来执行相应的操作。

  2. 属性(Attributes):也称为数据成员、实例变量或成员变量,用于描述类的状态或特征。属性可以是各种数据类型,如整数、浮点数、字符串等。每个对象都有自己的属性值,使得每个对象在相同的类基础上可以有不同的状态。

  3. 方法(Methods):也称为成员函数或操作,用于定义类的行为和操作。方法可以访问和操作类的属性,以及执行其他逻辑和算法。通过调用对象的方法,可以实现与对象的交互和操作。

  4. 封装(Encapsulation):封装是一种将数据和操作封装在类中的机制,通过限制对类的访问来保护数据的完整性和安全性。类通过公有(public)、私有(private)和保护(protected)等访问控制修饰符来控制成员的可见性和访问权限。

  5. 继承(Inheritance):继承是一种通过从一个现有类派生出新类的机制,新类继承了原始类的属性和行为。继承允许创建类的层次结构,其中派生类可以重用基类的代码,并添加、修改或扩展功能。

  6. 多态(Polymorphism):多态是一种允许以不同的方式处理不同类型对象的能力。多态性使得可以使用基类的指针或引用来引用派生类的对象,并在运行时选择正确的方法执行。

类是面向对象编程的核心概念之一,通过类的定义和实例化,可以创建具有特定属性和行为的对象。类提供了一种组织和封装数据和操作的方式,使得程序可以更加模块化、可扩展和可维护。

JY

类的关键字叫class class后面接的是类名, 类名首字母大写

#include 
#include
using namespace std;
class MobliePhone{
public: //public表示公共权限,类里面和类外都能访问
    //属性
    string brand;//品牌
    string model;//型号
    //方法
    void communicate(){
        cout<<"喂?"<<endl;
    }
    void play_video(){
        cout<<"小鲤鱼历险记"<<endl;
    }
    void play_music(){
        cout<<"光辉岁月"<<endl;
    }

}; //类的末尾不要忘记分号
int main()
{

}

1.2 类的实例化

类的实例化是指通过类创建对象的过程,也称为类的实例化或对象的创建。通过实例化类,可以创建具体的对象,以便在程序中使用对象的属性和方法。

在C++中,类的实例化遵循以下语法:

ClassName objectName;  // 创建对象的语法

其中,ClassName 是类的名称,objectName 是对象的名称。

例如,考虑以下简单的 Person 类的定义:

class Person {
public:
    std::string name;
    int age;

    void introduce() {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

可以使用该类来创建 Person 对象的实例:

Person person1;  // 创建一个 Person 对象的实例
person1.name = "John";  // 设置对象的属性
person1.age = 25;

person1.introduce();  // 调用对象的方法

在上述示例中,我们通过 Person 类创建了一个名为 person1 的对象。然后,我们可以使用点运算符(.)来访问对象的属性和调用对象的方法。

可以创建多个对象的实例,并根据需要对每个对象的属性进行初始化和操作。每个对象都是类的独立实体,具有自己的属性和状态。通过实例化类,可以在程序中同时处理多个对象,并对它们进行个别或集体的操作。

类的实例化还可以使用动态内存分配,通过使用 new 运算符来创建对象。例如:

Person* person2 = new Person();  // 使用动态内存分配创建对象
person2->name = "Alice";
person2->age = 30;

person2->introduce();

delete person2;  // 释放对象的动态内存

在这种情况下,使用 new 运算符创建了一个名为 person2Person 对象的实例,并使用指针来引用对象。然后,可以使用指针操作符 -> 访问对象的属性和方法。最后,通过 delete 运算符释放对象的动态内存。

通过类的实例化,可以在程序中创建并操作多个对象,实现面向对象编程的特性,如封装、继承和多态。每个对象都是类的实例,具有自己的状态和行为,可以根据需求进行个别或集体的操作。

JY

对象的概念:由类创建出来的具体实体
由类创建对象的过程叫类的实例化

对象的创建有两种方式;
1.栈内存对象
2.堆内存对象

栈内存对象和堆内存对象的区别:

  1. 栈内存对象出了作用范围就会自动销毁
    堆内存对象需要new关键字来创建,销毁时需要用到delete关键字,并把对象指针置为NULL。如果不销毁,堆内存对象所占用的内存空间不会自动释放,会造成内存泄漏,造成程序的卡顿甚至卡死。

  2. 栈内存对象通过.的方式操控方法
    堆内存对象是用一个对象指针来存储所创建对象的地址,调用方法使用->的方式。

#include 
#include
using namespace std;

class MobliePhone{
public: //public表示公共权限,类里面和类外都能访问
    //属性
    string brand;//品牌
    string model;//型号
    //方法
    void communicate(){
        cout<<"喂?"<<endl;
    }
    void play_video(){
        cout<<"小鲤鱼历险记"<<endl;
    }
    void play_music(){
        cout<<"光辉岁月"<<endl;
    }
    void show(){
        cout<<"品牌"<<brand<<" "<<"型号"<<model<<endl;
    }

}; //类的末尾不要忘记分号
int main()
{

    //栈内存对象
    MobliePhone mp1;
    mp1.brand="华为";
    mp1.model="P60";
    mp1.communicate();
    mp1.play_music();
    mp1.play_video();
    mp1.show();

    //堆内存对象
    MobliePhone * mp2=new MobliePhone;
    mp2->brand="魅族";
    mp2->model="20";
    mp2->show();
    mp2->communicate();
    mp2->play_music();
    mp2->play_video();
    delete mp2; //删除指针所指向内存 放弃对那块内存的管理权.
    cout<<&mp2<<endl;
    mp2=NULL; //最好将指针指向置为NULL
}

练习1

写一个矩形类,属性有width,length.求出周长 perimeter()和面积 area().

#include 
#include
using namespace std;
class Rectangle{
public:
    double length;
    double width;
    void perimeter(){
        cout<<"周长是:"<<(length+width)*2<<endl;
    }
    void area(){
        cout<<"面积是:"<<length*width<<endl;
    }
};
int main()
{
    Rectangle r1;
    r1.length=10;
    r1.width=2.2;
    r1.perimeter();
    r1.area();

    Rectangle * r2=new Rectangle;
    r2->length=5;
    r2->width=3;
    r2->perimeter();
    r2->area();
    delete r2;
    r2=NULL;

}

练习2

写一个Student类,类中有姓名,学号和年龄。给出一个show方法,可以打印处姓名和学号和年龄

#include 
#include
using namespace std;
class Student{
public:
    string name;
    string student_id;
    int age;
    void show(){
        cout<<"姓名:"<<name<<" "<<"学号:"<<student_id
           <<" 年龄:"<<age<<endl;
    }

};
int main()
{
    Student s1;
    s1.name="小明";
    s1.student_id="001";
    s1.age=20;
    s1.show();

    Student s2; //如果没有给属性赋值而直接使用,数据值会有问题
    s2.show();  //姓名: 学号: 年龄:82

}

2. 封装

概念

概念:是将属性和行为在形式上形成一个整体(类),把类中的一些属性和行为的实现细节进行隐藏。如果访问需要留有公共接口
通常情况下隐藏是将属性设为private(私有)权限,需要根据要求给出相应的读取或者写入接口

#include 
#include
using namespace std;
class Person{
private: //默认不写就是私有权限
    string name;//可读可写
    string address; //只读
    string password="1234";//只写
public:
    void get_name(){
        cout<<name<<endl;
    }
    void set_name(string n){
        name=n;
    }
    void get_address(){ //读取接口
        cout<<"济南"<<endl;
    }
    void set_password(string p){ //写入接口
        password=p;
    }
};
int main()
{
  Person p;
  p.set_name("小红");
  p.set_password("123456");

  p.get_address();
  p.get_name();

}

练习

属性有 name , student_id, score , password
要求:
姓名可读可写
学号是只读 分数只读
密码只写

#include 
#include
using namespace std;
class Student{
private:
    string name;
    string student_id;
    double score;
    string password;
public:
    void set_name(string n){
        name=n;
    }
    void get_name(){
        cout<<name<<endl;
    }
    void get_student_id(){
        cout<<"202301"<<endl;
    }
    void get_score(){
        cout<<"80分"<<endl;
    }
    void set_password(string p){
        password=p;
    }
};
int main()
{
    Student s2;
    s2.set_name("张三");
    s2.set_password("123789");

    s2.get_score(); //读取分数
    s2.get_student_id();//读取学号
    s2.get_name(); //读取姓名

}

3.构造函数

3.1 概念

构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的数据成员。构造函数的名称与类名相同,并且没有返回类型(包括void类型)。构造函数在对象创建时自动调用,负责初始化对象的初始状态。

构造函数的概念和特点如下:

  1. 对象的创建:构造函数在创建对象时自动调用,不需要显式地调用。当使用类名和合适的参数列表创建对象时,会调用与之对应的构造函数。

  2. 初始化数据成员:构造函数负责对对象的数据成员进行初始化。通过构造函数,可以在对象创建时将数据成员初始化为指定的值,或者执行一些特定的初始化操作。

  3. 构造函数重载:与普通函数一样,构造函数也可以进行重载,即在同一个类中定义多个具有不同参数列表的构造函数。通过构造函数重载,可以根据不同的初始化需求选择合适的构造函数。

  4. 默认构造函数:如果没有显式地定义构造函数,编译器会为类生成一个默认构造函数。默认构造函数没有参数,通常执行默认的初始化操作,如将数据成员初始化为零值或空值。

  5. 参数化构造函数:参数化构造函数带有参数,可以根据传入的参数值来初始化对象的数据成员。通过参数化构造函数,可以自定义对象的初始化过程,使得对象的创建更加灵活和具有个性化。

  6. 初始化列表:构造函数可以使用初始化列表(initializer list)来初始化数据成员。初始化列表使用冒号(:)后跟成员初始化语句,可以在构造函数体执行之前完成对数据成员的初始化。

构造函数在对象创建时起到了重要的作用,确保对象的数据成员得到正确的初始化。构造函数可以根据需要执行一系列初始化操作,确保对象在创建后处于一种合适的初始状态。通过构造函数的重载和参数化,可以根据不同的需求选择适当的构造函数进行对象的初始化。

JY

构造函数的作用:给对象中的属性进行初始化。
构造函数的特点:
1.与类同名
2.没有返回值
创建一个对象的时候,必须调用构造函数,如果不写,系统会有一个默认无参的构造函数。如果给出构造函数,默认的构造函数就不存在
默认的构造函数函数体为空,并未给属性进行赋值

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:
    //如果不写构造函数,编译器会给出下方默认的构造函数
    //MobliePhone(){}  
    MobliePhone(){
        cout<<"无参构造函数"<<endl;
    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }
};
int main()
{
  MobliePhone m;
  m.show();
}

3.2 有参构造函数

有参构造函数(Parameterized Constructor)是一种带有参数的构造函数,用于在对象创建时通过传入参数来初始化对象的数据成员。与默认构造函数不同,有参构造函数需要提供参数列表,以便传递初始化所需的值。

有参构造函数的特点如下:

  1. 参数列表:有参构造函数在定义时需要指定参数列表,以接收传递给构造函数的参数值。参数列表指定了构造函数需要的参数类型和参数名称。

  2. 初始化数据成员:有参构造函数可以使用参数列表中的值来初始化对象的数据成员。通过将参数的值分配给相应的数据成员,可以在对象创建时将数据成员设置为特定的初始值。

  3. 个性化初始化:有参构造函数可以根据传入的参数值执行个性化的初始化操作。通过传递不同的参数值,可以在创建对象时根据需求定制对象的初始状态。

  4. 构造函数重载:可以在同一个类中定义多个有参构造函数,使用不同的参数列表。通过构造函数重载,可以根据传入的参数类型和数量来选择合适的构造函数。

下面是一个示例,展示了一个带有参数的构造函数的定义和使用:

class Person {
public:
    std::string name;
    int age;

    Person(const std::string& personName, int personAge) {
        name = personName;
        age = personAge;
    }

    void introduce() {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person1("John", 25);  // 使用有参构造函数创建对象
    person1.introduce();

    return 0;
}

在上述示例中,Person 类定义了一个有参构造函数,接收一个 std::string 类型的参数和一个整型参数。构造函数使用传入的参数值来初始化对象的数据成员 nameage。然后,通过调用 introduce() 方法,可以打印对象的属性。

通过有参构造函数,可以灵活地初始化对象的数据成员,并根据需要进行个性化的初始化操作。有参构造函数提供了一种更具定制性的对象创建方式,使得对象的初始化更加灵活和适应不同的场景需求。

JY

给出构造函数,默认的构造函数就不存在

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:

    MobliePhone(string b,string m,int w){
        brand=b;
        model=m;
        weight=w;
    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }
};
int main()
{
  //MobliePhone m; //错误 给出构造函数,默认构造函数就不存在
  MobliePhone m2("红米","note10",100);
  m2.show();

}

3.3 构造函数支持默认值

构造函数可以支持默认值(Default Arguments)。默认值允许在调用构造函数时省略某些参数,而构造函数将使用预先定义的默认值来初始化相应的参数。

要为构造函数的参数提供默认值,可以在参数声明中为其指定默认值。当调用构造函数时,如果省略了具有默认值的参数,将使用默认值来初始化该参数。

以下是一个示例,展示了带有默认参数值的构造函数的定义和使用:

class Person {
public:
    std::string name;
    int age;

    Person(const std::string& personName = "Unknown", int personAge = 0) {
        name = personName;
        age = personAge;
    }

    void introduce() {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person1;  // 使用默认参数创建对象
    person1.introduce();

    Person person2("John");  // 仅提供部分参数,其他参数使用默认值
    person2.introduce();

    Person person3("Alice", 30);  // 提供所有参数,不使用默认值
    person3.introduce();

    return 0;
}

在上述示例中,Person 类的构造函数中的参数都具有默认值。如果在创建对象时省略了参数,构造函数将使用默认值初始化相应的参数。在 main() 函数中,我们创建了几个 Person 对象,其中一些使用了默认参数,而另一些提供了特定的参数值。

通过为构造函数的参数提供默认值,可以在创建对象时灵活地选择是否提供特定参数,并且省略的参数将使用默认值进行初始化。这使得构造函数在处理多种情况时更加方便和灵活,同时提供了更好的代码可读性和易用性。

JY

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:

    MobliePhone(string b,string m,int w=100){
        brand=b;
        model=m;
        weight=w;
    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }

};
int main()
{
  //MobliePhone m; //错误 给出构造函数,默认构造函数就不存在

  MobliePhone m2("红米","note10",200);
  m2.show();

  MobliePhone m3("小米","10");
  m3.show();
}

3.4 构造函数支持函数重载

构造函数可以进行函数重载(Function Overloading)。函数重载是指在同一个类中定义多个具有相同名称但参数列表不同的函数。

构造函数的函数重载可以用于提供不同的初始化方式,以满足不同的对象创建需求。通过重载构造函数,可以根据传入的参数类型、参数数量或参数顺序的不同来选择合适的构造函数进行对象的初始化。

下面是一个示例,展示了构造函数的函数重载的定义和使用:

class Person {
public:
    std::string name;
    int age;

    Person() {
        name = "Unknown";
        age = 0;
    }

    Person(const std::string& personName) {
        name = personName;
        age = 0;
    }

    Person(const std::string& personName, int personAge) {
        name = personName;
        age = personAge;
    }

    void introduce() {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person1;  // 调用无参构造函数
    person1.introduce();

    Person person2("John");  // 调用带有一个参数的构造函数
    person2.introduce();

    Person person3("Alice", 30);  // 调用带有两个参数的构造函数
    person3.introduce();

    return 0;
}

在上述示例中,Person 类中定义了三个构造函数进行函数重载。第一个构造函数没有参数,用于提供默认的初始化方式。第二个构造函数接收一个 std::string 类型的参数,用于初始化 name 数据成员。第三个构造函数接收一个 std::string 类型的参数和一个整型参数,用于初始化 nameage 数据成员。

通过构造函数的重载,可以根据不同的参数情况选择适当的构造函数进行对象的初始化。这样,可以为对象创建提供更多的灵活性和选择性。根据参数的不同组合,可以选择合适的构造函数进行对象的初始化,以满足具体的需求。

JY

构造函数支持函数默认值和函数重载,所以默认值和重载不要一起使用

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:

    MobliePhone(string b,string m,int w){
        brand=b;
        model=m;
        weight=w;
    }

    MobliePhone(){
        brand="iphone";
        model="14";
        weight=90;
    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }

};
int main()
{

  MobliePhone m2("红米","note10",200); //调用三个参数的构造函数
  m2.show();

  MobliePhone m4; //调用的无参构造函数
  m4.show();

}

3.5 构造初始化列表

构造函数初始化列表(Constructor Initialization List)是一种在构造函数定义中使用冒号(:)后跟初始化语句的方式,用于对类的数据成员进行初始化。

使用构造函数初始化列表可以在对象创建时直接对数据成员进行初始化,而不需要在构造函数体内逐个赋值。这种方式的优势在于能够提高初始化效率并确保数据成员以正确的方式初始化。

以下是一个示例,展示了使用构造函数初始化列表的方式进行数据成员初始化:

class Person {
public:
    std::string name;
    int age;

    Person(const std::string& personName, int personAge)
        : name(personName), age(personAge) {
    }

    void introduce() {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person1("John", 25);
    person1.introduce();

    return 0;
}

在上述示例中,Person 类的构造函数使用了构造函数初始化列表来初始化数据成员 nameage。在初始化列表中,通过冒号后跟成员初始化语句 name(personName)age(personAge),将传入的参数值直接赋给对应的数据成员。

使用构造函数初始化列表可以在对象创建时直接对数据成员进行初始化,而不需要在构造函数体内使用赋值操作。这样可以提高初始化效率,并确保数据成员以正确的方式初始化,尤其是对于类中的常量成员或引用成员等特殊类型。

另外,构造函数初始化列表还可以用于调用基类的构造函数或调用其他成员对象的构造函数。

总而言之,构造函数初始化列表提供了一种优雅和高效的方式来初始化类的数据成员,并可以确保正确的初始化顺序和初始化方式。它是C++中构造函数的重要特性之一,用于提高代码的可读性和性能。

JY

可以给属性进行初始化的操作

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:

//    MobliePhone(string b,string m,int w){
//        brand=b;
//        model=m;
//        weight=w;
//    }
    MobliePhone(string b,string m,int w):brand(b),model(m),weight(w){}

    MobliePhone(){
        brand="iphone";
        model="14";
        weight=90;
    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }

};
int main()
{

  MobliePhone m2("红米","note10",200); //调用三个参数的构造函数
  m2.show();

  MobliePhone m4; //调用的无参构造函数
  m4.show();

}

3.6 拷贝构造函数

拷贝构造函数(Copy Constructor)是一种特殊的构造函数,用于创建一个新对象并将其初始化为与现有对象相同的值。拷贝构造函数通常用于执行深拷贝(Deep Copy),以确保新对象拥有独立的内存副本,而不仅仅是简单地复制指针或引用。

拷贝构造函数的特点如下:

  1. 参数:拷贝构造函数的参数是一个常量引用,指向要拷贝的对象的类型。通常以类的引用方式作为参数。

  2. 创建新对象:拷贝构造函数用于创建一个新对象,该对象将被初始化为与传入的对象相同的值。

  3. 值复制:拷贝构造函数通常执行值复制操作,将传入对象的数据成员复制到新对象中。这可以确保新对象与原对象的数据值相同。

  4. 深拷贝:如果类中包含指针或动态分配的资源,拷贝构造函数应该执行深拷贝操作,以便新对象拥有独立的资源副本,而不是共享相同的资源。

以下是一个示例,展示了拷贝构造函数的定义和使用:

class Person {
public:
    std::string name;
    int age;

    // 拷贝构造函数
    Person(const Person& other) {
        name = other.name;
        age = other.age;
    }

    // 构造函数
    Person(const std::string& personName, int personAge) {
        name = personName;
        age = personAge;
    }

    void introduce() {
        std::cout << "My name is " << name << " and I am " << age << " years old." << std::endl;
    }
};

int main() {
    Person person1("John", 25);

    // 使用拷贝构造函数创建新对象
    Person person2 = person1;
    person2.introduce();

    return 0;
}

在上述示例中,Person 类中定义了拷贝构造函数。拷贝构造函数接收一个 Person 类型的常量引用作为参数,将传入对象的值复制到新对象中。在 main() 函数中,我们使用拷贝构造函数将 person1 对象复制给了 person2 对象。

拷贝构造函数的使用场景包括函数参数传递时的对象拷贝、函数返回值的对象拷贝,以及对象之间的赋值操作等。通过拷贝构造函数,可以创建新的对象,并确保新对象与原对象具有相同的值。如果类中包含指针或动态分配的资源,拷贝构造函数应该执行深拷贝操作,以确保每个对象都有独立的资源副本。

JY

拷贝构造构造函数用于拷贝构建对象,用已经存在的对象创建新的对象。
如果不给出拷贝构造函数,编译器会给出默认拷贝构造函数
拷贝构造函数的参数有两种形式: 对象的引用 或者 const修饰的对象引用

两个对象中的属性值,完全相同。拷贝构造函数创建的对象,是两个相互独立的实体,只是属性值相同

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:

    MobliePhone(string b,string m,int w):brand(b),model(m),weight(w){}

    //默认的拷贝构造函数 也会完成下面属性之间值的复制
    MobliePhone(MobliePhone & other){
        cout<<"拷贝构造函数调用"<<endl;
        brand=other.brand;
        model=other.model;
        weight=other.weight;

    }
    void set_model(string m){ //更改型号的接口
        model=m;

    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }

};
int main()
{
  MobliePhone mp1("小米","10pro",100);
  MobliePhone mp2(mp1); //用mp1拷贝构造创建mp2
  mp1.show();
  mp2.show();
  cout<<&mp1<<" "<<&mp2<<endl;//0x61fe74 0x61fe68
  mp1.set_model("12"); //更改mp1的model属性,两个个对象相互独立。mp2不受影响
  mp1.show();  //小米 12 100
  mp2.show();  //小米 10pro 100
}

3.7 拷贝构造函数调用的场景

1.用已经存在的对象创建新的对象
2.当函数的参数是对象类型的

#include 
#include
using namespace std;
class MobliePhone{
private:
    string brand;
    string model;
    int weight;
public:

    MobliePhone(string b,string m,int w):brand(b),model(m),weight(w){}

      MobliePhone(MobliePhone & other){
        cout<<"拷贝构造函数调用"<<endl;
        brand=other.brand;
        model=other.model;
        weight=other.weight;

    }
    void set_weight(int w){
        weight=w;
    }
    void show(){
        cout<<brand<<" "<<model<<" "<<weight<<endl;
    }

};

void test(MobliePhone m){

    m.set_weight(200);//更改weight为200
}
int main()
{
  //1.用已经存在的对象创建新的对象
  MobliePhone mp1("小米","10pro",100);
  //显示调用拷贝构造
  MobliePhone mp2(mp1);
   //隐式调用拷贝构造
  MobliePhone mp3=mp1;

  //2.当函数的参数是对象类型的
  test(mp3);
  mp3.show(); //小米 10pro 100

}

3.8 深拷贝与浅拷贝

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在对象拷贝过程中涉及的两个概念,用于描述对象成员的复制方式。

浅拷贝是指将一个对象的值复制到另一个对象,包括对象的数据成员和指针成员的值。在浅拷贝中,新对象的指针成员将指向原对象中相同的内存地址,这意味着新旧对象共享相同的资源。如果在浅拷贝对象中释放指针指向的内存,会导致原对象和浅拷贝对象都无法访问有效的内存。

深拷贝是指在对象拷贝过程中,创建一个新对象并将原对象的所有数据成员复制到新对象中,包括指针成员。深拷贝会为新对象的指针成员分配新的内存空间,并将原对象指针指向的内容复制到新内存中。这样,原对象和新对象将具有独立的资源副本,彼此之间没有共享关系。如果在深拷贝对象中释放指针指向的内存,不会影响原对象的有效性。

下面是一个示例,展示了浅拷贝和深拷贝的区别:

#include 
#include 

class Person {
public:
    std::string name;
    int* age;

    // 拷贝构造函数 - 浅拷贝
    Person(const Person& other) {
        name = other.name;
        age = other.age;
    }

    // 构造函数
    Person(const std::string& personName, int personAge) {
        name = personName;
        age = new int(personAge);
    }

    // 析构函数
    ~Person() {
        delete age;
    }
};

int main() {
    Person person1("John", 25);

    // 浅拷贝
    Person person2 = person1;
    std::cout << "person1 age: " << *person1.age << std::endl;
    std::cout << "person2 age: " << *person2.age << std::endl;

    // 修改浅拷贝对象的值,会影响原对象
    *person2.age = 30;
    std::cout << "person1 age: " << *person1.age << std::endl;
    std::cout << "person2 age: " << *person2.age << std::endl;

    return 0;
}

在上述示例中,Person 类中的浅拷贝构造函数将指针 age 直接复制给新对象,导致两个对象共享相同的内存。当修改浅拷贝对象的指针成员时,原对象也会受到影响,因为它们指向同一块内存空间。

为了解决这个问题,可以使用深拷贝来创建独立的资源副本,

JY

浅拷贝

默认的拷贝构造函数,只时对象之间
当对象的属性有指针的时候,也只是简单的地址值的复制。导致两个对象的属性指向了同一块内存。破化了了对象之间的独立性

#include 
#include 
#include 
using namespace std;
class MobilePhone{
private:
    char * brand;
public:
    MobilePhone(char * n){
        brand=n;
    }
    void show(){
        cout<<brand<<endl; //指针存的字符串的地址
        //c++输出语句 cout见到char * 类型的会输出字符串内容
        printf("%p \n",brand); //c语言的形式打印brand中存的地址
    }

};
int main()
{
    char a[20]="xiaomi";
    cout<<"数组的地址:"<<&a<<endl; //0x61fe7c
    MobilePhone mp(a);
    mp.show(); //xiaomi

    MobilePhone mp1(mp);
    mp1.show(); //xiaomi
    cout<<"------------"<<endl;
    strcpy(a,"redmi");
    mp.show(); //redmi
    mp1.show(); //redmi
}

深拷贝

当对象属性中有指针类型的时候,不简单的地址复制,而是开辟新的内存空间,拷贝内容代替复制地址

#include 
#include 
#include 
using namespace std;
class MobilePhone{
private:
    char * brand;
public:
    MobilePhone(char * n){
        //brand=n; //这样是地址值的复制
        brand=new char[20]; 开辟新的内存空间
        strcpy(brand,n);//只是拷贝内容
    }
    void show(){
        cout<<brand<<endl; //brand指针存的字符串的地址
        //而c++输出语句 cout见到char * 类型的会输出字符串内容
        cout<<(void *)brand<<endl;


    }
    MobilePhone(MobilePhone & other){
         //brand=other.brand //默认是这种赋值
        brand=new char[20]; //开辟新的内存空间
        strcpy(brand,other.brand);//只是拷贝内容
    }

};
int main()
{
    char a[20]="xiaomi";
    cout<<"数组的地址:"<<&a<<endl; //0x61fe7c
    MobilePhone mp(a);
    mp.show(); //xiaomi
    strcpy(a,"redmi");
    cout<<"a数组内容更改之后"<<endl;
    mp.show();  //xiaomi
    cout<<"---------"<<endl;

    MobilePhone mp2(mp); //默认拷贝构造函数只是简单值的复制
    mp2.show();
}

深拷贝与浅拷贝例题

浅拷贝

#include 
#include 
#include 
using namespace std;
class Phone{    
private:
    int * weight;
public:
    Phone(int n){
        weight=new int(n); //开辟一块堆内存 把n的值放进去
    }
    void show(){
        cout<<weight<<endl;
    }
};
int main()
{
  Phone p1(10);
  p1.show(); //0x6417a8

  Phone p2(p1);
  p2.show(); //0x6417a8

}

深拷贝

#include 
#include 
#include 
using namespace std;
class Phone{
private:
    int * weight;
public:
    Phone(int n){
        weight=new int(n); //开辟一块内存 把n的值放进去
    }
    void show(){
        cout<<weight<<endl;
    }

    Phone(Phone & p){
        int temp=*(p.weight);
        weight=new int(temp);

    }

};
int main()
{
  Phone p1(10);
  p1.show(); //0x6f17a8

  Phone p2(p1);
  p2.show(); //0x6f17b8

}

4. 析构函数

析构函数(Destructor)是一种特殊的成员函数,在对象生命周期结束时被调用,用于执行对象的清理和资源释放操作。析构函数的名称与类名相同,但在名称前加上波浪线(~)作为标识符。

析构函数在以下情况下被调用:

  1. 对象离开作用域:当对象的作用域结束时,例如当对象在函数内部声明并在函数结束时销毁时,析构函数将被调用。

  2. 对象被销毁:当使用 delete 运算符显式销毁通过 new 创建的对象时,析构函数将被调用。

  3. 容器元素销毁:当对象是容器(如数组、向量、列表等)的元素,并且容器本身被销毁时,析构函数将被调用。

析构函数通常用于执行以下操作:

  1. 释放动态分配的资源:如果对象在构造函数中分配了内存或资源,析构函数应该在对象销毁时释放这些资源,以避免内存泄漏或资源泄漏。

  2. 清理对象状态:析构函数可以用于清理对象的状态,例如关闭文件、释放锁、关闭网络连接等。

下面是一个示例,展示了析构函数的定义和使用:

class Person {
public:
    std::string name;

    // 构造函数
    Person(const std::string& personName) {
        name = personName;
        std::cout << "Person " << name << " constructed." << std::endl;
    }

    // 析构函数
    ~Person() {
        std::cout << "Person " << name << " destructed." << std::endl;
    }
};

int main() {
    {
        Person person1("John");
        Person person2("Alice");
    }  // 作用域结束,析构函数被调用

    Person person3("Bob");
    delete new Person("Tom");  // 创建并销毁对象,析构函数被调用

    return 0;
}

在上述示例中,Person 类中定义了构造函数和析构函数。在 main() 函数中,我们创建了多个 Person 类型的对象,并在不同的作用域中进行了实例化。当对象离开作用域时,析构函数被自动调用,输出相应的析构消息。

注意,使用 delete 运算符销毁通过 new 创建的对象时,会首先调用析构函数,然后释放分配的内存。

析构函数在管理资源和确保对象正确清理方面起着重要的作用。通过析构函数,可以在对象销毁时执行必要的清理操作,以避免资源泄漏和不确定的行为。

JY

析构函数作用:对象销毁的做一些善后工作,对象中用new关键字创建的内容进行销毁。
析构函数特点:
1.对象销毁时自动调用
2.形式:~类名() 没有参数
3.如果不显示给出,会有默认的析构函数。函数体为空
堆内存对象,用new关键字创建,销毁时需要用到delete。如果没手动销毁,就会造成内存泄漏

#include 
#include 
#include 
using namespace std;
class Cat{
private:
    string name;
public:
    Cat(string n){
        name=n;
    }
    ~Cat(){
        cout<<name<<"挂掉了"<<endl;
    }
};
void test(){
    Cat c("小花"); //c是栈内存对象 出了作用范围自动销毁
    Cat * c2=new Cat("小白"); //堆内存对象
    delete c2;
    c2=NULL;
}
int main()
{
    test();
}

用new关键字创建的内容需要在析构函数中进行销毁

#include 
#include 
#include 
using namespace std;
class Phone{
private:
    int * weight;
public:
    Phone(int n){
        weight=new int(n); //开辟一块内存 把n的值放进去
    }
    void show(){
        cout<<weight<<endl;
    }

    Phone(Phone & p){

        int temp=*(p.weight);
        weight=new int(temp);

    }
    ~Phone(){
        cout<<"调用析构函数"<<endl;
        delete weight;
        weight=NULL;
    }

};
int main()
{
    { //局部代码块 p1和p2出了这个范围就会销毁,自动调用析构函数
      Phone p1(10);
      p1.show(); //0x6f17a8

      Phone p2(p1);
      p2.show(); //0x6f17b8
    }

}

5. 名字空间

概念及运用

命名空间(Namespace)是一种用于组织代码和标识符的机制,用于将全局命名空间划分为更小的、更可管理的单元。命名空间提供了一种避免名称冲突的方式,允许在不同的作用域中定义相同名称的标识符。

命名空间可以包含变量、函数、类、结构体和其他命名空间等。通过将相关的代码放在同一个命名空间中,可以提高代码的可读性和可维护性。

以下是一个示例,展示了命名空间的定义和使用:

// 命名空间的定义
namespace MyNamespace {
    int x = 5;

    void foo() {
        std::cout << "Hello from MyNamespace!" << std::endl;
    }

    // 嵌套命名空间
    namespace InnerNamespace {
        void bar() {
            std::cout << "Hello from InnerNamespace!" << std::endl;
        }
    }
}

int main() {
    // 使用命名空间中的变量和函数
    std::cout << MyNamespace::x << std::endl;  // 输出: 5
    MyNamespace::foo();  // 输出: Hello from MyNamespace!
    MyNamespace::InnerNamespace::bar();  // 输出: Hello from InnerNamespace!

    return 0;
}

在上述示例中,我们定义了一个名为 MyNamespace 的命名空间,并在其中定义了一个整数变量 x 和一个函数 foo()。还演示了如何在 main() 函数中使用命名空间中的变量和函数。

命名空间可以嵌套,其中一个命名空间可以包含另一个命名空间。在示例中,MyNamespace 包含了嵌套命名空间 InnerNamespace,其中定义了一个函数 bar()

可以使用命名空间限定符(Namespace Qualifier)来访问命名空间中的成员。例如,MyNamespace::x 用于访问命名空间 MyNamespace 中的变量 x

使用命名空间可以避免名称冲突,并提供了更好的代码组织和模块化能力。命名空间是C++中的重要特性,被广泛用于项目开发和库设计中。

JY

作用

命名空间是为了区分不同区域的同名全局变量或者函数。

#include 
using namespace std;
int a=20;
namespace myspace { //自己定义命名空间
    int a=100;
    int b=200;
}
using namespace myspace; //使用自定义的命名空间
int main(){
    int a=10;
    cout<<a<<endl;
    cout<<::a<<endl;
    cout<<myspace::a<<endl;
    cout<<b<<endl; //没有重名的变量 可以不用指定属于哪个名字空间
}

命名空间的使用

当类的成员类中声明,类外定义的时候,定义实现时需要加上名字空间,表示属于哪个范围
string Test::get_str()
string:函数的返回值
Test:: 属于哪个范围
get_str():函数名

#include 
using namespace std;
class Test{
private:
    string str;
public:
    Test(string s); //构造函数声明
    string get_str();//成员函数声明
};
Test::Test(string s){ //构造函数类外实现
    str=s;
}
string Test::get_str(){ //成员函数类外实现
    return str;
}

int main(){
    Test t("one");
    cout<<"得到的属性值:"<<t.get_str()<<endl;

}

你可能感兴趣的:(嵌入式学习,c++,学习)