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

目录

面向对象基础

1. 类和对象(重点)

1.1 类的定义

1.2 创建对象

2. 封装(重点)

3. 构造函数 constructor(重点)

3.1 基础使用

3.2 构造初始化列表

3.3 构造函数的调用方式(掌握)

3.4 拷贝构造函数

3.4.1 概念

3.4.2 浅拷贝

3.4.3 深拷贝

4. 析构函数 destructor(重点)

5. 作用域限定符 ::

5.1 名字空间(掌握)

5.2 类内声明,类外定义(重点)

5.3 配合静态使用

6. this关键字

6.1 概念(掌握)

6.2 调用成员(掌握)

6.3 区分重名的成员变量与局部变量(掌握)

6.4 链式调用(熟悉)

7. static关键字

7.1 静态局部变量(掌握)

7.2 静态成员变量(掌握)

7.3 静态成员函数(掌握)

7.4 单例模式(了解)

8. const关键字

8.1 修饰成员函数(掌握)

8.2 修饰对象(掌握)

8.3 修饰成员变量(掌握)

8.4 修饰局部变量

8.5 constexpr 常量表达式(熟悉)


面向对象基础

                                                --------------主要讲解面向对象编程中最基础的概念

1. 类和对象(重点)

类:是一个抽象的概念,用于描述同一类对象的特征。在现在学习阶段,一个单独的类没有任何功能。

对象:根据类的描述创造的实体。

1.1 类的定义

类中主要包含两部分:

  • 属性 property成员变量 member value或 数据成员)

在类中存在的变量,用于存储数据,通常是一个名词,例如身高、价格、颜色......

  • 行为(成员函数 member function

可以执行的功能,是类中的函数,通常是一个动词或动词词组,例如:吃饭、运行、关闭......

成员 = 成员变量 + 成员函数

【例子】定义一个手机类。

#include <iostream>

using namespace std;

/**
 * @brief The MobilePhone class
 * 手机类
 */
class MobilePhone // 帕斯卡(大驼峰)命名法:所有单词首字母大写
{
public: // 表示访问不受限
    string brand; // 品牌
    string model; // 型号
    int weight; // 重量

    void play_music()
    {
        cout << "Remedy" << endl;
    }

    void run_game()
    {
        cout << "炉石传说" << endl;
    }

    void communicate()
    {
        cout << "喂?" << endl;
    }

};

1.2 创建对象

C++支持两种对象:

  • 栈内存对象

在生命周期(生命周期为所在的{})结束后,自动被回收,调用成员使用.

  • 堆内存对象

必须使用new关键字创建对象,使用指针保存对象首地址,使用delete销毁对象,如果创建后没有手动销毁,对象会持续存在(内存泄漏),调用成员使用->(在Qt Creator中按.键自动转为->)

#include <iostream>

using namespace std;

class MobilePhone
{
public:
    string brand;
    string model;
    int weight;

    void play_music()
    {
        cout << "Remedy" << endl;
    }

    void run_game()
    {
        cout << "炉石传说" << endl;
    }

    void communicate()
    {
        cout << "喂?" << endl;
    }

};

int main()
{
    // 栈内存对象
    MobilePhone mp1;
    // 调用成员变量,先赋值,再读取输出
    mp1.brand = "苹果";
    mp1.model = "16 Pro Max";
    mp1.weight = 200;
    cout << mp1.brand << endl;
    cout << mp1.model << endl;
    cout << mp1.weight << endl;
    // 调用成员函数
    mp1.communicate();
    mp1.play_music();
    mp1.run_game();

    // 堆内存对象
    MobilePhone* mp2 = new MobilePhone;
    mp2->brand = "华为";
    mp2->model = "非凡大师xt1";
    mp2->weight = 301;
    cout << mp2->brand << endl;
    cout << mp2->model << endl;
    cout << mp2->weight << endl;
    mp2->communicate();
    mp2->play_music();
    mp2->run_game();
    delete mp2; // 销毁mp2
    // 不要销毁后还使用
//    cout << mp2->brand << endl;
//    mp2->communicate();

    return 0;
} // mp1销毁

2. 封装(重点)

上面的代码与结构体非常相似,因为结构体就是一种完全开放的类。类通常需要进行封装,封装指的是先将类的一些属性和细节隐藏,再重新提供外部调用接口。

#include <iostream>

using namespace std;

class MobilePhone
{
private: // 私有:被修饰的成员只能在类内访问
    string brand; // 读写
    string model = "16"; // 只读
    int weight; // 只写

public:
    string get_brand() // getter:读函数
    {
        return brand;
    }

    void set_brand(string b) // setter:写函数
    {
        brand = b;
    }

    string get_model()
    {
        return model;
    }

    void set_weight(int w)
    {
        weight = w;
    }
};

int main()
{
    MobilePhone mp1;
    // 调用setter设置属性值
    mp1.set_brand("华为");
    mp1.set_weight(300);
    // 调用getter获取属性值
    cout << mp1.get_brand() << endl;
    cout << mp1.get_model() << endl;

    // TODO 可以试试堆内存对象

    return 0;
}

封装的意义在于让程序员站在外部视角看待整个对象整体,忽略内部细节,同时可以提升代码的安全性和可维护性。

3. 构造函数 constructor(重点)

3.1 基础使用

类中有一种特殊的成员函数,在创建对象时必须调用,这个函数就是构造函数,构造函数的特殊性体现在:

  • 不写返回值
  • 函数名称必须是类名
  • 如果程序员不手动编写构造函数,编译器会自动添加一个无参且函数体为空的构造函数。

构造函数经常用于在创建对象时进行对象属性初始化,构造函数也支持参数默认值和重载。

#include <iostream>

using namespace std;

class MobilePhone
{
private:
    string brand;
    string model;
    int weight;

public:
    // 编译器会在程序员不写的情况下添加下面的构造函数
//    MobilePhone(){}

    MobilePhone()
    {
        // 给属性赋予初始值
        brand  = "山寨";
        model = "???";
        weight  = 188;
        cout << "构造函数" << endl;
    }

    MobilePhone(string b,string m,int w)
    {
        brand = b;
        model = m;
        weight = w;
        cout << "构造函数2" << endl;
    }

    /**
     * @brief show 输出所有属性值
     */
    void show()
    {
        cout << brand << endl;
        cout << model << endl;
        cout << weight << endl;
    }
};

int main()
{
    MobilePhone mp1;
    mp1.show();

    MobilePhone* mp2 = new MobilePhone;
    mp2->show();
    delete mp2;

    MobilePhone mp3("魅族","Lucky08",199);
    mp3.show();

    MobilePhone* mp4 = new MobilePhone("小米","su7",2100000);
    mp4->show();
    delete mp4;

    cout << "主函数结束" << endl;
    return 0;
}

3.2 构造初始化列表

在当前阶段,下面的两种写法是等效。

3.3 构造函数的调用方式(掌握)

构造函数可以显式调用,也可以隐式调用。

显式调用:在创建对象时使用明确的构造函数调用语法,不能被explicit关键字影响。

隐式调用:在创建对象时不使用明确的构造函数调用语法,受到explicit关键字影响。

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    Student(string n):name(n)
    {
        cout << "构造函数,name=" << name << endl;
    }

    string get_name()
    {
        return name;
    }
};


int main()
{
    Student s1("张三");
    cout << s1.get_name() << endl;

    Student* s2 = new Student("李四");
    cout << s2->get_name() << endl;
    delete s2;

    string name = "王五";
    Student s3 = name; // 编译器帮忙调用了构造函数
    cout << s3.get_name() << endl;

    Student s4(name); // 编译器帮忙调用了构造函数
    cout << s4.get_name() << endl;

    return 0;
}

使用explicit关键字可以屏蔽隐式调用的构造函数。

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    // 明确的
    explicit Student(string n):name(n)
    {
        cout << "构造函数,name=" << name << endl;
    }

    string get_name()
    {
        return name;
    }
};


int main()
{
    Student s1("张三"); // 显式
    cout << s1.get_name() << endl;

    Student* s2 = new Student("李四"); // 显式
    cout << s2->get_name() << endl;
    delete s2;

    string name = "王五";
//    Student s3 = name; // 隐式,被explicit屏蔽
//    cout << s3.get_name() << endl;

    Student s4(name); // 显式
    cout << s4.get_name() << endl;

    return 0;
}

3.4 拷贝构造函数

3.4.1 概念

如果程序员不手动编写拷贝构造函数,编译器会为每个类增加一个拷贝构造函数。

通过拷贝构造函数创建的新对象,只是属性值与源对象相同,但是会在新的地址空间进行开辟。

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    Student(string n):name(n)
    {
        cout << "构造函数,name=" << name << endl;
    }

    // 编译器自动添加的拷贝构造函数
//    Student(const Student& s)
//    {
//        name = s.name;
//    }


    Student(const Student& s)
    {
        name = s.name;
        cout << "拷贝构造函数" << endl;
    }

    string get_name()
    {
        cout << &name << endl;
        return name;
    }
};


int main()
{
    Student s1("张三");
    cout << s1.get_name() << endl;

    Student s2(s1);
    cout << s2.get_name() << endl;

    return 0;
}

3.4.2 浅拷贝

当类中的成员变量出现指针时,默认的拷贝构造函数会出现浅拷贝的现象。

#include <iostream>
#include <string.h> // 头文件

using namespace std;

class Student
{
private:
    char* name;

public:
    Student(char* n):name(n)
    {
        cout << "构造函数" <<  endl;
    }

    char* get_name()
    {
        return name;
    }
};


int main()
{
    char name[20] = "张三";

    Student s1(name);
    Student s2(s1);

    strcpy(name,"李四");

    cout << s1.get_name() << endl; // 李四
    cout << s2.get_name() << endl; // 李四

    return 0;
}

在上面的代码中,在31行修改了26行的字符数组内容,s1和s2隐藏的成员变量的值就变了,且s1和s2的name保存的地址就是26行的name,这都不符合面向对象的特性。

3.4.3 深拷贝

深拷贝的思路是在浅拷贝每次对指针进行赋值时,都单独开辟一块内存给要赋值的变量。

#include <iostream>
#include <string.h> // 头文件

using namespace std;

class Student
{
private:
    char* name;

public:
    Student(char* n):name(new char[20])
    {
        // 同步两块内存的内容
        strcpy(name,n);
        cout << "构造函数" <<  endl;
    }

    Student(const Student& s)
    {
        // 同步两块内存的内容
        name = new char[20];
        strcpy(name,s.name);
        cout << "拷贝构造函数" << endl;
    }


    char* get_name()
    {
        return name;
    }
};


int main()
{
    char name[20] = "张三";

    Student s1(name);
    Student s2(s1);

    strcpy(name,"李四");

    cout << s1.get_name() << endl; // 张三
    cout << s2.get_name() << endl; // 张三

    return 0;
}

在上面的深拷贝代码中,new出来的空间并没有delete回收,因此会造成内存泄漏问题。

4. 析构函数 destructor(重点)

析构函数是与构造函数对立的函数,也是一种特殊的成员函数。

构造函数

析构函数

函数名称为类名

函数名称为~类名

功能为创建对象并初始化

功能为在对象销毁时回收资源

在创建对象时调用

在对象销毁时自动被调用

有参数,支持重载和默认值

无参数,不支持重载和默认值

程序员不手写析构函数,编译器也会添加一个下面格式的析构函数:

~类名(){}
#include <iostream>

using namespace std;

class Dog
{
public:
    ~Dog()
    {
        cout << "析构函数" << endl;
    }
};


int main()
{
    Dog d1;
    Dog* d2 = new Dog;
    delete d2; // d2输出:析构函数

    cout << "主函数结束" << endl;
    return 0;
} // d1输出:析构函数

回到深拷贝代码中,可以在析构函数里释放堆内存资源,在Student中添加下面的代码:

5. 作用域限定符 ::

5.1 名字空间(掌握)

名字空间是一种代码的层级划分。

#include <iostream>

using namespace std;
// C++课程中几乎所有的类型(不包括基本数据类型)都在std中

int a = 1;

// 新建一个名字空间
namespace my_space {
    int a = 3;
    string s = "哈哈哈";
}

namespace my_space2 {
    int a = 4;
    string s = "嘿嘿嘿";
}

// 使用自己的名字空间
using namespace my_space2;


int main()
{
    int a = 2;
    std::cout << "你好" << std::endl;
    cout << a << endl; // 2
    cout << ::a << endl; // 1

    // 访问my_space的内容
    cout << my_space::a << my_space::s << endl;
    // 访问my_space2的内容
    cout << my_space2::a << s << endl;

    return 0;
}

5.2 类内声明,类外定义(重点)

函数是可以声明定义分离的,成员函数也可以声明定义分离,把成员函数的声明放置在类内,把成员函数的定义放置在类外,因为定义在类外,所以需要类名::来制定定义的函数是哪个类的。

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    // 类内声明
    Student(string n);
    string get_name();
    void set_name(string n);
};

// 类外定义
Student::Student(string n)
{
    name = n;
}

string Student::get_name()
{
    return name;
}

void Student::set_name(string n)
{
    name = n;
}

int main()
{
    Student s("张三");
    s.set_name("张三丰");
    cout << s.get_name() << endl;

    return 0;
}

5.3 配合静态使用

在后续static关键字中讲解。

6. this关键字

6.1 概念(掌握)

this在类中表示一个指针:this指针,是一种特殊的指针,保存了当前类对象的首地址(指向了当前类的对象)。、

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    Student(string n):name(n)
    {
        cout << this << endl;
    }

    string get_name()
    {
        cout << this << endl;
        return name;
    }

    void set_name(string n)
    {
        cout << this << endl;
        name = n;
    }
};



int main()
{
    Student s("张三");
    cout << &s << endl;
    s.set_name("张三丰");
    cout << s.get_name() << endl;

    cout << endl;

    Student* s2 = new Student("李四");
    cout << s2 << endl;
    cout << s2->get_name() << endl;

    return 0;
}

6.2 调用成员(掌握)

结论:成员必须由对象调用。

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    Student(string n)
    {
        this->name = n;
        cout << this->get_name() << endl;
    }

    string get_name()
    {
        return this->name;
    }

    void set_name(string n)
    {
        this->name = n;
    }
};

上面的代码中,所有在类内调用成员的位置,如果程序员不写this指针(实际上没有必要手写,比较繁琐),编译器都会在成员调用之前添加this指针。

6.3 区分重名的成员变量与局部变量(掌握)

#include <iostream>

using namespace std;

class Student
{
private:
    string name;

public:
    Student(string name)
    {
        this->name = name;
    }

    string get_name()
    {
        return name;
    }

    void set_name(string name)
    {
        this->name = name;
    }
};



int main()
{
    Student s("Tom");
    cout << s.get_name() << endl;
    s.set_name("Jerry");
    cout << s.get_name() << endl;

    return 0;
}

6.4 链式调用(熟悉)

结论:如果一个成员函数的返回值是当前类型的引用,说明这个函数支持链式调用,return的内容一定是*this。

#include <iostream>

using namespace std;

class Value
{
private:
    int value;

public:
    Value(int value)
    {
        this->value = value;
    }

    Value& add(int value)
    {
        this->value += value;
        return *this;
    }

    int get_value()
    {
        return value;
    }
};



int main()
{
    Value v1(0);
    // 普通的调用方式:分步操作
    v1.add(1);
    v1.add(2);
    v1.add(4);
    v1.add(7);
    cout << v1.get_value() << endl; // 14

    cout << endl;

    Value v2(0);
    // 链式调用
    cout << v2.add(1).add(2).add(4).add(7).get_value() << endl; // 14
    cout << v2.get_value() << endl; // 14

    return 0;
}

7. static关键字

7.1 静态局部变量(掌握)

使用static修饰的局部变量就是静态局部变量,所在的函数第一次被调用时创建,直到程序运行结束销毁,所有的对象共用这个一个静态局部变量。

#include <iostream>

using namespace std;

class Test
{
public:
    void test_static()
    {
        int a = 1;
        static int b = 1; // 局部静态变量
        cout << a++ << endl;
        cout << b++ << endl << endl;
    }
};

int main()
{
    Test t1;
    Test t2;
    t1.test_static();
    t2.test_static();
    t1.test_static();

    return 0;
}

7.2 静态成员变量(掌握)

使用是static关键字修饰的成员变量就是静态成员变量。

静态成员变量具有以下特点:

1. 非const修饰的静态成员变量不能在类内初始化,必须在类外初始化。

2. 同一个类的所有对象共享一份静态成员变量。

3. 静态成员变量可以直接通过类名调用,无需关联任何对象,因为静态成员变量在程序执行时创建,在程序结束时销毁。

#include <iostream>

using namespace std;

class Test
{
public:
    int a = 1; // 成员变量
    static int b; // 静态成员变量
};

int Test::b = 1; // 类外初始化

int main()
{
    // 通过类名直接调用静态成员变量
    cout << Test::b << " " << &Test::b << endl;

    Test t1;
    Test t2;
    cout << t1.a++ << " " << &t1.a << endl;
    cout << t2.a++ << " " << &t2.a << endl;
    cout << t1.b++ << " " << &t1.b << endl;
    cout << t2.b++ << " " << &t2.b << endl;

    return 0;
}

7.3 静态成员函数(掌握)

使用static修饰的成员函数就是静态成员函数,静态成员函数具有以下特点:

1. 没有this指针,因此不能调用非静态成员。

2. 如果静态成员函数声明与定义分离,static只需要写在声明处即可。

3. 除了可以使用对象调用外,还可以直接通过类名::调用,更加推荐后者。

#include <iostream>

using namespace std;

class Test
{
public:
    int a = 1;
    static int b;

    static void func(); // 静态成员函数
};

int Test::b = 1;

void Test::func() // 类外定义
{
//        cout << a << endl; 错误
    cout << b << endl;
}

int main()
{
    Test t;
    t.func();

    Test::func();

    return 0;
}

7.4 单例模式(了解)

模式设计(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

单例 = 单实例

实例 instance = 对象 object

单例模式可以让类的对象全局创建时保持一个。

基于指针的单例模式(非完全版):

#include <iostream>

using namespace std;

/**
 * @brief The Singleton class 单例模式的类
 */
class Singleton
{
private: // 构造函数私有化防止外部调用
    Singleton(){}
    Singleton(const Singleton&){}

    static Singleton* instance; // 类内的对象,即单例

public:
    static Singleton* get_instance()
    {
        if(instance == NULL)
            instance = new Singleton;
        return instance;
    }
};

Singleton* Singleton::instance = NULL;


int main()
{
    Singleton* s1 = Singleton::get_instance();
    cout << s1 << endl;
    Singleton* s2 = Singleton::get_instance();
    cout << s2 << endl;

    return 0;
}

基于引用的单例模式:

#include <iostream>

using namespace std;

/**
 * @brief The Singleton class 单例模式的类
 */
class Singleton
{
private: // 构造函数私有化防止外部调用
    Singleton(){}
    Singleton(const Singleton&){}

public:
    static Singleton& get_instance()
    {
        static Singleton instance;
        return instance;
    }
};

int main()
{
    Singleton& s1 = Singleton::get_instance();
    Singleton& s2 = Singleton::get_instance();
    cout << &s1 << " " << &s2 << endl;

    return 0;
}

8. const关键字

8.1 修饰成员函数(掌握)

const修饰的成员函数,表示常成员函数,可以调用非const的成员变量,但是不能修改数值,不能调用非const的成员函数。

如果常成员函数的声明与定义分离,const在声明和定义处都要使用。

#include <iostream>

using namespace std;

class Test
{
private:
    string name;
public:
    void set_name(string name)
    {
        this->name = name;
    }

    string get_name() const; // 常成员函数

    void test_const() const // 常成员函数
    {
//        set_name("哈哈哈"); 错误
        cout << get_name() << endl;
        cout << name << endl;
//        name = "你好"; 错误
    }
};

string Test::get_name() const // 类外定义
{
    return name;
}

int main()
{
    Test t;
    t.set_name("再见");
    t.test_const();

    return 0;
}

建议只要成员函数不修改属性值就使用const修饰,以提升代码的安全性,例如getter等。

8.2 修饰对象(掌握)

const修饰对象表示该对象是常量对象,这样的对象的成员变量数值不可变,不能调用任何非const修饰的成员函数。

#include <iostream>

using namespace std;

class Test
{
public:
//    string name = "张三"; // 直接初始化可以

    string name;

//    Test(string name)
//    {
//        this->name = name; // 构造函数函数体初始化可以
//    }

    Test(string name):name(name){}

    void set_name(string name)
    {
        this->name = name;
    }

    string get_name() const
    {
        return name;
    }
};



int main()
{
    // const可以放在类型前后
    Test const t1("张三");
//    t1.set_name("你好"); 错误
    const Test t2("李四");
//    t2.name = "你好"; 错误
    cout << t1.get_name() << endl;
    cout << t2.get_name() << endl;

    return 0;
}

8.3 修饰成员变量(掌握)

const修饰的成员变量表示常成员变量,这样的成员变量值不允许被修改。

常成员变量的初始化方式有两种:

  • 直接初始化
  • 构造初始化列表(优先级更高)
#include <iostream>

using namespace std;

class Test
{
public:

    const string name = "张三"; // 直接初始化

    Test(string name):name(name) // 构造初始化列表
    {}

//    Test(string name)
//    {
//        this->name = name; 错误
//    }

    void set_name(string name)
    {
//        this->name = name; 错误
    }

    string get_name() const
    {
        return name;
    }
};


int main()
{
    Test t("王五");
    cout << t.get_name() << endl;

    return 0;
}

8.4 修饰局部变量

表示局部变量不可被修改,常见于const修饰引用参数。

代码略。

8.5 constexpr 常量表达式(熟悉)

constexpr是比const更严谨的关键字,表示编译期确定的常量值。

#include <iostream>

using namespace std;

class Test
{
public:
    constexpr static int a = 1; // a=1在编译期确定
//    constexpr int b = 2; 错误
    const int c = 3;
};

在上面的例子中,a不需要配合任何对象使用,而对象是在程序运行期间创建的,因此a可以被constexpr修饰,表示在编译期确定;反之b需要在对象创建之后才能创建,因此不能被constexpr修饰。

被constexpr修饰的内容表示在编译期间可以确定,C++中部分代码是需要在编译期间确定。

#include <iostream>
#include <array> // 后面要学习的一个头文件

using namespace std;

// 表示是否可以在编译期间计算出返回值
constexpr int calc_len(int i)
{
    return i+5; // 随便写的计算规则
}

int main()
{
    // 5表示创建的arr对象的长度,必须在编译期间确定
    array<int,5> arr;

    // 编译期间可以计算出结果为6,正确
    array<int,calc_len(1)> arr2;

    int i = 1;
    // 编译期间无法计算出最后结果,报错
//    array<int,calc_len(i)> arr3; 错误

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值