C++基础——类与对象

news2024/11/24 9:13:29

1 概述

C++是面向对象的语言,面向对象语言三大特性:封装、继承、多态。
C++将万事万物抽象为对象,对象上有其属性和行为。

2 封装

2.1 封装的意义

封装是面向对象的三大特性之一,封装将属性和行为作为一个整体,对属性和行为加以权限控制。
创建类语法:

class 类名 {
访问权限:
	属性;
	行为;
};

示例:

class Student {
private:
    string m_name;
    int m_age;
public:
    Student(string name, int age) {
        m_name = name;
        m_age = age;
    }
    string getName() {
        return m_name;
    }
};

上面除了定义属性和行为,也定义了权限访问控制符
C++包含三种访问控制符:

  • public 可被类中函数、子类函数、友元函数和类对象访问
  • protected 可被类中函数、子类函数、友元函数访问
  • private 可被类中函数和友元函数访问

2.2 struct和class区别

二者的区别在于默认的访问权限不同,struct默认访问权限为public,class默认访问权限为private

#include <iostream>
using namespace std;

class C1 {
    int m_A;
};

struct S1 {
    int m_A;
};


int main() {
    C1 c1;
    //c1.m_A; 报错,无法访问
    S1 s1;
    s1.m_A = 10;
    return 0;
}

2.3 成员属性设置为私有

通常,将成员属性设置为私有,并提供设置和获取的方法。
这样的好处是可以控制成员属性的访问权限,对于写权限,能够检测数据的有效性。

class Test {
public:
    //num1提供读写接口
    void setNum1(int num1) {
        m_num1 = num1;
    }

    int getNum1() {
        return m_num1;
    }

    //num2只提供读接口
    int getNum2() {
        return m_num2;
    }

    //num3只提供写接口,并判定数据范围
    void setNum3(int num3) {
        if (num3 > 0) {
            m_num3 = num3;
        } else {
            m_num3 = 0;
        }
    }

private:
    int m_num1;
    int m_num2;
    int m_num3;
};

封装的思想通常会将属性设置为私有,暴露必要的修改行为给外部。

3 对象创建和清理

3.1 构造函数和析构函数

一个类具有最基础的两个函数是构造函数和析构函数,即使程序员未添加这两个函数,编译器也会生成一个默认的版本。
其中构造函数用于为类创建一个对象,并进行一些初始化的工作。
析构函数与构造函数相反,是为了清理一个对象,并释放资源。
构造函数和析构函数都是由编译器自动调用。

class Test {
private:
	int m_num;
public:
    Test();
    Test(int num) {
		m_num = num;
	}

    ~Test();
};

构造函数可以重载,而析构函数只能有一个。构造函数可以通过传入不同的参数来重载,而析构函数没有参数。
当未定义构造函数和析构函数时,编译器会自动生成以下两个实现:

Test(){}
~Test(){}

默认会生成两个空实现。
而如果提供了有参构造函数,则编译器不会提供默认的空参构造函数,如果有此使用场景,则需要自己再提供一个空参构造函数。

3.2 构造函数分类及调用方式

构造函数有两种分类方式:
按参数分为:有参构造和无参构造
按类型分类:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐藏转换法

#include <iostream>

using namespace std;

class Person {
public:
    //无参构造函数
    Person() {
        cout << "无参构造函数" << endl;
    }

    //有参构造函数
    Person(int age) {
        cout << "有参构造函数" << endl;
        m_age = age;
    }

    //拷贝构造函数
    Person(const Person &person) {
        cout << "拷贝构造函数" << endl;
        m_age = person.m_age;
    }

    ~Person() {
        cout << "析构函数" << endl;
    }

public:
    int m_age;
};

void test1() {
    //调用无参构造函数
    Person p1;
    //显示调用无参构造函数
    Person p2 = Person();
    //调用拷贝构造函数
    Person P3(p1);
}

void test2() {
    //括号法
    Person p1(10);
    //括号不能用于调用无参构造函数,这样定义会被认为是一个函数声明
    //Person p();

    //显示法
    Person p2 = Person(10);
    Person p3 = Person(p2);

    //创建匿名对象,无法使用,直接析构
    Person();
    Person(10);

    //隐式转换法,隐式调用有参构造函数和拷贝构造函数
    Person p4 = 10;
    Person p5 = p4;

    //不能利用拷贝构造初始化匿名对象,会被认为是对象声明
    //Person p6(p4);
}

int main() {
    test1();
    test2();
}

输出:

无参构造函数
无参构造函数
拷贝构造函数
析构函数
析构函数
析构函数
有参构造函数
有参构造函数
拷贝构造函数
无参构造函数
析构函数
有参构造函数
析构函数
有参构造函数
拷贝构造函数
析构函数
析构函数
析构函数
析构函数
析构函数

比较需要注意的是隐藏转换法

3.3 拷贝构造函数的调用时机

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

  • 使用一个已有的对象去初始化另一个对象
  • 值传递的方式给对象类型参数传值
  • 以值的方式返回局部对象
#include <iostream>

using namespace std;

class Person {
public:
    //无参构造函数
    Person() {
        cout << "无参构造函数" << endl;
    }

    //有参构造函数
    Person(int age) {
        cout << "有参构造函数" << endl;
        m_age = age;
    }

    //拷贝构造函数
    Person(const Person &person) {
        cout << "拷贝构造函数" << endl;
        m_age = person.m_age;
    }

    ~Person() {
        cout << "析构函数" << endl;
    }

public:
    int m_age;
};

void test1() {
    //调用有参构造函数
    Person p1(10);
    //括号法调用拷贝构造函数
    Person p2(p1);
    //隐式转换法调用拷贝构造函数
    Person p3 = p1;
    //显示调用拷贝构造函数
    Person p4 = Person(p1);

    //赋值操作,不会调用拷贝构造函数
    Person p5;
    p5 = p1;
}

void doSomething1(Person p){}
void test2() {
    Person p;
    //对象赋值给形参,调用拷贝构造函数
    doSomething1(p);
}

Person doSomething2() {
    Person p;
    return p;
}
void test3() {
    Person p = doSomething2();
}

int main() {
    test1();
    test2();
    test3();
}

输出:

有参构造函数
拷贝构造函数
拷贝构造函数
拷贝构造函数
无参构造函数
析构函数
析构函数
析构函数
析构函数
析构函数
无参构造函数
拷贝构造函数
析构函数
析构函数
无参构造函数
析构函数

最后返回局部对象与预想中有差异是因为编译器优化,感兴趣可以搜索RVO了解。
由于返回值和值传递传对象都会导致对象复制,对象大小会随着属性的增加而增加,所以一般对象作为参数的时候,都是使用引用或者指针进行传参或返回。

3.4 构造函数调用规则

默认情况下,C++编译器会至少给一个类添加3个函数:

  • 默认构造函数(无参构造函数,实现为空)
  • 默认析构函数(空实现析构函数)
  • 默认拷贝构造函数(对定义的属性进行拷贝)

构造函数生成规则如下:

  • 如果用户定义有参构造函数,则编译器不会提供默认无参构造函数,但还是会提供默认拷贝构造函数
  • 如果用户定义拷贝构造函数,则编译器不会提供默认无参构造函数

1、使用编译器默认函数

class Person {
public:
    int m_age;
};

int main() {
    Person p1;
    p1.m_age = 10;
    Person p2 = p1;
    cout << "p2.m_age = " << p2.m_age << endl;
}

这里调用了默认无参构造和拷贝构造函数。析构函数由于没有实现所以无法体现。

2、提供有参构造的情况

class Person {
public:
    //无参构造函数
    //Person() {
    //    cout << "无参构造函数" << endl;
    //}

    //有参构造函数
    Person(int age) {
        cout << "有参构造函数" << endl;
        m_age = age;
    }

    //拷贝构造函数
    Person(const Person &person) {
        cout << "拷贝构造函数" << endl;
        m_age = person.m_age;
    }

    ~Person() {
        cout << "析构函数" << endl;
    }

public:
    int m_age;
};

在这里插入图片描述
没有匹配的构造函数调用,编译器不提供默认构造函数。
3、提供拷贝构造函数

class Person {
public:
    //拷贝构造函数
    Person(const Person &person) {
        cout << "拷贝构造函数" << endl;
        m_age = person.m_age;
    }

    ~Person() {
        cout << "析构函数" << endl;
    }

public:
    int m_age;
};

在这里插入图片描述
提供了拷贝构造函数后,不会提供默认无参构造函数。

3.5 深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝,指向同一个内存区域
深拷贝:在堆中重新申请空间,进行拷贝

class Person {
public:
    //无参(默认)构造函数
    Person() {
        cout << "无参构造函数!" << endl;
    }
    //有参构造函数
    Person(int age ,int height) {

        cout << "有参构造函数!" << endl;

        m_age = age;
        m_height = new int(height);

    }
    //拷贝构造函数  
    Person(const Person& p) {
        cout << "拷贝构造函数!" << endl;
        //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
        m_age = p.m_age;
        m_height = new int(*p.m_height);

    }

    //析构函数
    ~Person() {
        cout << "析构函数!" << endl;
        if (m_height != NULL)
        {
            delete m_height;
        }
    }
public:
    int m_age;
    int* m_height;
};

void test01()
{
    Person p1(18, 180);

    Person p2(p1);

    cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;

    cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}

int main() {

    test01();
    return 0;
}

由于类中的属性是在堆上分配的,如果使用浅拷贝,会导致两个对象的m_height都指向了同一个堆内存区域,而当一个对象析构之后,会释放该内存区域,而另一个对象还在使用该内存区域,会导致不确定的结果。而第二个对象析构时,则会导致重复释放堆区,会导致程序终止。所以如果有堆上分配内存的属性,需要使用深拷贝,重新申请堆内存区域进行赋值。
示例如下:

void test1() {
    Person p1(18, 180);
    {
        Person p2(p1);
        cout << "p2.height = " << *p2.m_height << endl;
    }
    cout << "p1.height = " << *p1.m_height << endl;
}

int main() {
    test1();
}

输出:

有参构造函数
p2.height = 180
析构函数
p1.height = 887973408
析构函数

Process finished with exit code -1073740940 (0xC0000374)

上面是注释了深拷贝的结果,p2正常访问,然后p2被析构,此时p1的m_height指向的内存实际已经释放,所以访问该内存会导致不确定的结果,然后p1会重复释放m_height指向的内存,导致程序出错终止。

3.6 初始化列表

C++提供了一种初始化列表的语法来初始化属性

class Person {
public:

    传统方式初始化
    //Person(int a, int b, int c) {
    //    m_A = a;
    //    m_B = b;
    //    m_C = c;
    //}

    //初始化列表方式初始化
    Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
    void PrintPerson() {
        cout << "mA:" << m_A << endl;
        cout << "mB:" << m_B << endl;
        cout << "mC:" << m_C << endl;
    }
private:
    int m_A;
    int m_B;
    int m_C;
};

int main() {
    Person p(1, 2, 3);
    p.PrintPerson();

    return 0;
}

初始化列表的语法可以省略原始构造函数中的一些模版代码。

3.7 类对象作为类成员

class Phone
{
public:
    Phone(string name)
    {
        m_PhoneName = name;
        cout << "Phone构造" << endl;
    }
    ~Phone()
    {
        cout << "Phone析构" << endl;
    }
    string m_PhoneName;
};

class Person
{
public:
    //初始化列表可以告诉编译器调用哪一个构造函数
    Person(string name, string pName) :m_Name(name), m_Phone(pName)
    {
        cout << "Person构造" << endl;
    }

    ~Person()
    {
        cout << "Person析构" << endl;
    }

    void playGame()
    {
        cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
    }

    string m_Name;
    Phone m_Phone;

};
void test01()
{
    //当类中成员是其他类对象时,我们称该成员为 对象成员
    //构造的顺序是 :先调用对象成员的构造,再调用本类构造
    //析构顺序与构造相反
    Person p("张三" , "苹果X");
    p.playGame();

}

int main() {
    test01();
}

先调用对象成员的构造函数,然后调用本类的构造函数。析构的时候正好与构造的时候顺序相反。

3.8 静态成员

静态成员就是在成员变量或者成员函数前加上static关键字

  • 静态成员变量
    • 所有对象共享同一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能访问静态成员变量
      1、静态成员变量
class Person
{

public:
    static int m_A; //静态成员变量
private:
    static int m_B; //静态成员变量也是有访问权限的
};
//类外初始化
int Person::m_A = 10;
int Person::m_B = 10;

void test01()
{
    //静态成员变量两种访问方式

    //1、通过对象
    Person p1;
    p1.m_A = 100;
    cout << "p1.m_A = " << p1.m_A << endl;

    Person p2;
    p2.m_A = 200;
    cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
    cout << "p2.m_A = " << p2.m_A << endl;

    //2、通过类名
    cout << "m_A = " << Person::m_A << endl;

    //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}

int main() {
    test01();
}

静态成员变量也有访问权限,与非静态成员变量的访问权限一样。静态成员变量可以通过对象和类名访问。

class Person
{
public:
    static void func()
    {
        cout << "func调用" << endl;
        m_A = 100;
        //m_B = 100; //错误,不可以访问非静态成员变量
    }

    static int m_A; //静态成员变量
    int m_B; // 
private:
    //静态成员函数也是有访问权限的
    static void func2()
    {
        cout << "func2调用" << endl;
    }
};
int Person::m_A = 10;

void test01()
{
    //静态成员变量两种访问方式

    //1、通过对象
    Person p1;
    p1.func();

    //2、通过类名
    Person::func();

    //Person::func2(); //私有权限访问不到
}

int main() {
    test01();
}

静态成员函数只能访问静态成员变量,也只能直接调用静态成员函数,可以在其中创建对象来调用非静态成员函数,因为非静态成员函数属于对象。静态成员函数也可以通过对象和类名两种方式调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1178483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

顺丰函证通API集成,无代码开发连接CRM和电商平台

1. 顺丰&#xff1a;全球第四大快递公司的无代码开发连接 顺丰是全球第四大快递公司&#xff0c;秉承 “以用户为中心&#xff0c;以需求为导向&#xff0c;以体验为根本” 的产品设计思维。顺丰不仅在国内市场深耕&#xff0c;而且横向拓展多元业务领域&#xff0c;纵深完善产…

Node Sass version 9.0.0 is incompatible with ^4.0.0.

1.错误产生原因&#xff1a; node、 node-sass 和sass-loader的版本对应问题 2.解决方案&#xff1a; 删除之前的 npm uninstall node-sass sass-loader 安装指定的 npm i node-sass4.14.1 sass-loader7.3.1 --save -dev

业绩持续增长,“创新与变革”是云南白药发展的不二法门?

提及云南白药&#xff0c;大多数消费者的第一反应便是云南白药气雾剂、云南白药牙膏等产品。事实上&#xff0c;随着消费需求驱动、行业升级走向愈发明确&#xff0c;云南白药早已启动从传统中药制造企业到现代化大健康企业的转型&#xff0c;并持续产出成果。 近日&#xff0…

Kubernetes技术与架构-存储 4

如上所示&#xff0c;Kubernetes集群支持动态申请存储资源&#xff0c;即集群管理员可以按照实际的需求动态地申请存储资源&#xff0c;集群管理员需要事先定义一个或者多个StorageClass存储类型的资源&#xff0c;Pod中的容器实例直接引用事先定义的StorageClass存储类型的资源…

开关电源泄漏电流测试方法| 万用表测量开关电源漏电流的方法及接线方式分享

漏电流测试是开关电源安规测试项目之一&#xff0c;目的是为了检测漏电流是否超过了额定标准&#xff0c;防止漏电流过大造成设备损毁&#xff0c;甚至引发电击安全事故。漏电流测试方法多样&#xff0c;纳米软件将带你了解如何用万用表测量开关电源的漏电流。 开关电源漏电流测…

Squid

一、Squid 代理服务器 Squid 主要提供缓存加速、应用层过滤控制的功能。 二、代理的工作机制 1&#xff0e;代替客户机向网站请求数据&#xff0c;从而可以隐藏用户的真实IP地址。 2&#xff0e;将获得的网页数据&#xff08;静态 Web 元素&#xff09;保存到缓存中并发送给…

关于 国产系统UOS系统Qt开发Tcp服务器外部连接无法连接上USO系统 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/134254817 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

接口自动化测试分层设计与实践总结01

本文以笔者当前使用的自动化测试项目为例&#xff0c;浅谈分层设计的思路&#xff0c;不涉及到具体的代码细节和某个框架的实现原理&#xff0c;重点关注在分层前后的使用对比&#xff0c;可能会以一些伪代码为例来说明举例。 接口测试三要素&#xff1a; 参数构造 发起请求&…

【Redis】SSM整合Redis注解式缓存的使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Redis》。&#x1f3af;&#x1f3af; &#x1f4…

2000-2022年上市公司供应链数字化示范名单匹配数据

2000-2022年上市公司供应链数字化示范名单匹配数据 1、时间&#xff1a;2000-2022年 2、来源&#xff1a;商务部 3、指标&#xff1a; 上市公司供应链数字化&#xff08;根据城市名单匹配&#xff09;&#xff1a;股票代码、年份、股票简称、中文全称、省份、城市、区县、上…

接口请求断言

接口请求断言是指在发起请求之后&#xff0c;对返回的响应内容去做判断&#xff0c;用来查看是否响应内容是否与规定的返回值相符。 在发起请求后&#xff0c;我们使用一个变量 r 存储响应的内容&#xff0c;也就是 Response 对象。 Response 对象有很多功能强大的方法可以调…

[动态规划] (九) 路径问题:LeetCode 64.最小路径和

[动态规划] (九) 路径问题&#xff1a;LeetCode 64.最小路径和 文章目录 [动态规划] (九) 路径问题&#xff1a;LeetCode 64.最小路径和题目解析解题思路状态表示状态转移方程初始化和填表顺序返回值 代码实现总结 64. 最小路径和 题目解析 (1) 从左上角到右下角 (2) 只能向右…

Poetry:Python开发者的依赖管理新时代

更多资料获取 &#x1f4da; 个人网站&#xff1a;涛哥聊Python 在Python开发中&#xff0c;管理项目的依赖关系是一个至关重要的任务。传统上&#xff0c;开发者使用requirements.txt文件和pip工具来管理依赖&#xff0c;但这种方式在复杂项目中存在一些问题。Poetry是一个现…

Docker 学习路线 5:在 Docker 中实现数据持久化

Docker 可以运行隔离的容器&#xff0c;包括应用程序和其依赖项&#xff0c;与主机操作系统分离。默认情况下&#xff0c;容器是临时的&#xff0c;这意味着容器中存储的任何数据在终止后都将丢失。为了解决这个问题并在容器生命周期内保留数据&#xff0c;Docker 提供了各种数…

kafka问题汇总

报错1&#xff1a; 解决方式 1、停止docker服务   输入如下命令停止docker服务 systemctl stop docker 或者service docker stop1   停止成功的话&#xff0c;再输入docker ps 就会提示出下边的话&#xff1a; Cannot connect to the Docker daemon. Is the docker daem…

通过全流量查看部门或客户端网络使用情况

近年来&#xff0c;随着数字化转型和云计算服务的广泛应用&#xff0c;组织和企业对于网络带宽和性能的需求也在不断增长。 网络的稳定性、性能和安全性对于业务流程的顺畅运行至关重要。因此&#xff0c;了解部门或客户端网络的使用情况是网络管理和优化的关键。本文将通过Ne…

【C++】STL容器适配器——stack类的使用指南(含代码使用)(17)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一、stack 类——基本介绍二、stack 类…

if语句中的按位取反问题

&#x1f380; 文章作者&#xff1a;二土电子 &#x1f338; 关注公众号获取更多资源&#xff01; &#x1f438; 期待大家一起学习交流&#xff01; 文章目录 一、现象描述1.1 在C语言中&#xff08;非STM32&#xff09;1.2 STM32中运行 二、基础知识复习2.1 原码、反码和补…

dell r720部署chatglm3,使用nvidia tesla P40+M40

dell r720的idrac的地址默认是192.168.1.110&#xff0c;root 默认密码calvin fatal Error! All channnels have been disabled due to all DIMMs failed the Memoey 是什么意思 Dell PowerEdge T320服务器 开机显示 Fatal Errort!all channells have been disabled due to …

NSSCTF第11页(1)

[HUBUCTF 2022 新生赛]Calculate 进到主页 翻译 回答以下数学问题20次&#xff1b;你有3秒钟的时间来解决每个问题&#xff1b; 为了保护服务器&#xff0c;你不能在1秒内回答一个问题 您已回答0个问题&#xff1b; 让我们3秒速算&#xff0c;没那个实力&#xff0c;提示说是写…