C++面向对象基础

发布于:2024-10-12 ⋅ 阅读:(9) ⋅ 点赞:(0)

目录

一.函数

1.内联函数

2.函数重载

3.哑元函数

二.类和对象

2.1 类的定义

2.2 创建对象

三. 封装(重点)

四. 构造函数 constructor(重点)

4.1 基础使用

4.2 构造初始化列表

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

4.4 拷贝构造函数

4.4.1 概念

4.4.2浅拷贝

4.4.3深拷贝

五. 析构函数 destructor(重点)


一.函数

1.内联函数

        内涵函数的正确使用可以提升程序的执行效率。内联函数用于取代C语言中的宏定义函数。内联函数在编译的时候,直接把函数展开到主函数中进行编译,在运行期间减少调用的开销。

通常将具有以下性质的函数写为内联函数:
        ● 代码长度在5行以内
        ● 不包含复杂的控制语句
        ● 频繁的被调用

#include <iostream>
using namespace std;
inline void print_string(string str)
{
    cout << str << endl;
}
int main()
{
    print_string("helloworld");

    return 0;
}
关键字:inline

后续学习的成员函数默认添加inline修饰。
我们手动添加上inline关键字,将函数声明为内联函数,。但是这个不是我们可以决定的,编译器有自己的判断准则。我们只是非常卑微的给编译器提一个建议,具体是否变为内联函数,还是编译器自己决定的。

2.函数重载

        C++中允许多个函数使用同一个名称,这种用法就是函数重载。函数重载要求函数名称相同,但是参数不同(类型或者数量不同或者前后顺序不同)。与返回值类型等其他因素无关。
#include <iostream>
using namespace std;

void print_show(int i)
{
    cout << "调用int重载:" << i << endl;
}

// 错误 名称相同,参数相同编译器无法区分
//void print_show(int i)
//{
//    cout << "调用int重载:" << i << endl;
//}

void print_show(string str)
{
    cout << "调用了string重载:" << str << endl;
}


//int print_show(const int i)
//{
//    cout << "调用int2重载:" << i << endl;
//}


//void print_show(float i)
//{
//    cout << "调用float重载:" << i << endl;
//}

void print_show(double i)
{
    cout << "调用double重载:" << i << endl;
}


int main()
{
    print_show(2.3);
    return 0;
}

3.哑元函数

        函数的参数只有类型,没有名称,有这样参数的函数就是哑元函数。

#include <iostream>
using namespace std;

// 哑元函数
void print_show(int)
{
    cout << "调用了int哑元函数" << endl;
}


int main()
{
    print_show(1);
    return 0;
}
作用1:哑元函数用来区分函数重载

作用2:运算符重载中用到
作用3:保持向前兼容

二.类和对象

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

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

2.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;
    }
};

2.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销毁

三. 封装(重点)

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

#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;
}
        封装的意义在于程序员站在外部视角看待整个对象整体忽略内部细节同时可以提升代码安全性可维护性

四. 构造函数 constructor重点)

4.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;
}

4.2 构造初始化列表

当前阶段下面两种写法等效

4.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;
}

4.4 拷贝构造函数

4.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;
}

4.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,这都不符合面向对象的特性。

4.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回收,因此会造成内存泄漏问题。

五. 析构函数 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中添加下面的代码: