C++笔记:类和对象(一)->封装

类和对象

        认识类和对象

         先来回忆一下C语言中的类型和变量,类型就像是定义了数据的规则,而变量则是根据这些规则来实际存储数据的容器。类是我们自己定义的一种数据类型,而对象则是这种数据类型的一个具体实例。类就可以理解为类型,而对象就可以理解为变量,而且对象是更加高级的变量。

        类型 = 类型数据 + 类型操作

        比如double类型

        他的数据类型占用8字节,并且可以表示成小数。

        类型操作可以加减乘除,但是它不支持模(%)运算。

        那么类就是包含了类型数据和类型操作的一个集合。

        也可以理解为数据就是类的属性,操作就是类的行为

        通过下面我定义的一个人的类,那么height,sex, age都是这个类的类型数据,而类型操作就是函数run和函数speak。

class People {
    double height;//身高
    char sex;//性别
    int age;//年龄
    string name;//姓名
    void run() {//跑
        cout << "I can run" << endl;
    }
    void speak() {//说话
        cout << "I can speak" << endl;
    }
};

        那么我需要一个特定的人的时候,我就需要定义一个对象用来存这个特定人的属性。

        下面的用类定义一个对象,并且对他的属性进行赋值。

People a;
a.name = "Zhang san";
a.age = 18;
a.sex = '1';//女0,男1
a.height = 1.89;

        而我需要这个人进行说话或者跑时,就需要调用他相应的操作,也就是函数。

a.run();
a.speak();

        访问权限

        public:是公共的资源,是谁都可以访问的资源,比如空气,阳光,公共场合都是谁都可以访问进行使用的。

        private:是私有的资源,是只能类自己进行访问的资源,比如每个人自己的私人物品,是只能自己使用和访问的。

        protected:是受保护的资源,是只能自己访问以及自己的子类访问的资源,比如你儿子可以使用你的东西。(在后续文章继承时会用到protected权限)

        friendly:是在关键字friend声明后,它允许被声明后的类或函数访问这一个类的私有或受保护的资源,比如你的好兄弟在你的允许下可以访问和使用你的私人物品。(在后续的文章中会更新到使用)

        重点:这里的访问权限是规定的类外对该类类内的访问权限。

        比如我在一个类中定义了一个访问权限为私有的方法,那么我只能在这个类的内部进行调用,在类外是无法调用的,除非我对这个方向进行了friend友元声明。

        对于为什么要为什么要有访问权限的设置,看如下代码:

#include<iostream>
using namespace std;


class People {
public :
int age;
    void speak() {
        cout << "my age is "<< age << endl;
        return ;
    }
};


int main() {
    People a;
    a.age = -100;
    a.speak();
    return 0;
}

        如果对于age是public公有访问的,那么在设置年龄的将不会受到限制,对于这份代码可能没有问题,如果在以后设计项目或者工程时,就会造成灾难性的错误。

        那么正确的写法应该如下,把age设置为私有的,对age赋值时,定义一个函数来进行对age赋值,并判断值是否在正确范围内,对于获取age值也定义一个函数进行来获取。

#include<iostream>
using namespace std;


class People {
public :
    void speak() {
        cout << "my age is "<< age << endl;
        return ;
    }
    void set_age(int x) {
        if (age <= 0 || age > 1000) {
            perror("your age is error\n");
            exit(1);
        }
        age = x;
        return ;
    }
    int get_age() {
        return age;
    }
private :
    int age;
};


int main() {
    People a;
    a.set_age(-100);
    a.speak();
    return 0;
}

构造函数和析构函数

        定义一个对象的流程:

     对于构造函数的详细解析如下代码:

#include<iostream>
using namespace std;

class A {
public :
    A() {//默认构造函数
        cout << "default constructor" << endl;
    }
    //如果这里没有const关键字,传入的对象是const声明的那么就会报错
    //如果没有&(引用),那么将会造成无限套娃的拷贝构造
    //假如这里没有&,那么这里的形参就相当于实参给他赋值
    //而赋值就是 A a = b, b是实参传入的值
    //那这里就会又调用构造函数,形成无限套娃
    A(const A &a) {
        this->x = a.x;
        this->y = a.y;
        cout << "copy constructor" << endl;
    }
    A(int x, int y) {//有参构造函数
        cout << "have parameter constructor" << endl;
        this->x = x;//this表示当前对象的地址
        this->y = y;
    }
    void operator=(const A &a) {//这个函数是=运算符重载,是类中默认有的
        cout << "this is assigment"  << endl;
        return ;
    }
    A(int x) {//转换构造函数
        cout << "conversion constructor" << endl;
        this->x = x;
        this->y = 0;
    }
    void output() {
        cout << "(" << x << ", " << y << ")" << endl;
    }
    ~A() {//析构函数
        cout << "destructor" << endl;
    }
private :
    int x, y;
};


void func(A a) {
    cout << "func : ";
    a.output();
    return ;
}


int main() {
    A a;//调用默认构造函数
    cout << "1-------------------------" << endl;
    A b(1, 2);//调用有参构造
    cout << "2-------------------------" << endl;
    //这里就调用了拷贝构造函数
    A e = b;
    cout << "3-------------------------" << endl;
    //这里只是运用了普通赋值运算符
    //通过执行会发现没有执行任何构造函数
    a = b;
    cout << "4-------------------------" << endl;
    //调用转换构造,将int类型转换为A类型
    //这里也可以是另一个类型的对象,那也是转换构造
    //不过还需要从新定义一个需要转换构造的函数,里面的参数就是对应的类型
    A c(3);
    cout << "5-------------------------" << endl;
    //这里调用的是拷贝构造函数,将对象b进行拷贝给函数形参a
    func(b);
    func(c);
    cout << "6-------------------------" << endl;
    //对于这中函数调用情况
    //这里就会用到转换构造
    //他会隐式的将int类型5通过转换构造转会为A类型
    func(5);
    cout << "7-------------------------" << endl;
    //这里相当于A d(4);
    //这里可以这样理解
    //等号类型相同编程才能通过
    //那么4,int类型就会通过转换构造变成A类型
    //如果没有转换构造那么就编译无法通过
    A d = 4;
    a = 6;
    cout << "8-------------------------" << endl;
    return 0;
}

构造函数的初始化列表

构造函数中的初始列表使用:

#include<iostream>
using namespace std;

class C {
public:
    C(int n) {
        this->num = n;
        cout << "C coversion constructor" << endl;
    }
    void output() {
        cout << num;
    }
private :
    int num;
};

class A {
public :
    //:后面是构造函数的初始化列表
    A(int a) : a(a), b(10), c(6) {
        cout << "A constructor" << endl;
    }
    void output() {
        cout << " " <<a << " " << b << " ";
        c.output();
        cout << endl;
        return ;
    }
private :
    int a, b;
    C c;
};


int main() {
    A a(2);
    a.output();
    return 0;
}

构造函数和析构函数的调用顺序

如下这份代码:

#include <iostream>
#include <string>
using namespace std;


class A {
public :
    A() {};
    A(string n)  {
        name = n;
        cout << name << " class A construtor" << endl;
    }
    ~A() {
        cout << name << " class A destructor" << endl;
    }
int *p;
private :
    string name;
};

class B {
public :
    //:a(a)
    //:后面是构造函数时,最该对象中的成员属性进行初始化操作
    B(string n, A &a) : a(a){
        name = n;
        cout << name << " class B construtor" << endl;
        //new关键字可以理解为,C语言中malloc去堆区中开辟一段空间
        a.p = new int[10];
        return ;
    }
    A &a;//创建一个引用属性,这里&a可以暂时理解为取a的地址
    ~B() {
        //delete就相当于C语言中的free对开辟空间的释放
        delete a.p;
        cout << name << " class B destructor" << endl;
    }
private :
    string name;
};


int main() {
    A a("a");
    B b("b", a);
    return 0;
}

执行结果:

        可以发现,析构顺序是构造顺序反过来的,通过代码可以发现在B中的有参构造中用到了a对象的引用,然后对a对象的p指针开辟了一段空间,如果析构时顺序和构造顺序一样的那么,在析构b对象时,去delete a.p去释放开辟的空间时,a对象已经被析构掉了,那就也无法去找a对象中的指针p了。

类属性与方法 

        对于上面的代码的类中,它们只有成员属性和成员方法。

        就拿文章中的第一份代码来看,对于属性,也就是类中的变量,他所存储的值是跟着对象改变而改变的,而类中的两个方法(函数),也是随着对象改变而改变的,因为它们的输出也是不同的。所以这些叫做成员属性和成员方法。

class People {
    double height;//身高
    char sex;//性别
    int age;//年龄
    string name;//姓名
    void run() {//跑
        cout << name << " can run" << endl;
    }
    void speak() {//说话
        cout << name << " can speak" << endl;
    }
};

 下面这份代码是对成员属性和成员方法的使用:

#include<iostream>
using namespace std;


class Point {
    public :

    Point() : x(0), y(0) {} //默认构造
    //区分x和y
    //在x(x),括号外是成员属性x,括号内是形参x
    Point(int x, int y) : x(x), y(y) {} //有参构造
    void set_x (int x) { this->x = x; }
    void set_y (int y) { this->y = y; }
    int get_x() { 
        //访问类中的类属性
        Point::get_x_cnt += 1;
        return this->x; 
    }
    int get_y() { return this->y; }
    //这里是直接定义了类方法
    static int x_cnt() {
        return Point::get_x_cnt;
    }
    private :
    //这里只是声明了这个变量
    //get_x_cnt,记录变量x在被get时,在整个程序执行时被调用了多少次
    static int get_x_cnt;
    int x, y;
};

//这里就是定义了get_x_cnt, 并且给他的值进行了初始化
int Point::get_x_cnt = 0;



int main() {
    Point p1(3, 4), p2(5, 6);
    cout << p1.get_x() << ", " << p1.get_y() << endl;
    cout << p2.get_x() << ", " << p2.get_y() << endl;
    p1.get_x();
    p1.get_x();
    p1.get_x();
    cout << "x_cnt : " << Point::x_cnt() << endl;
    return 0;
}

 const 方法

对于const和mutable关键字使用,对于上一份代码的修改:

#include<iostream>
using namespace std;


class Point {
    public :
    Point() : x(0), y(0) , get_x_cnt(0){} //默认构造
    Point(int x, int y) : x(x), y(y), get_x_cnt(0) {} //有参构造
    void set_x (int x) { this->x = x; }
    void set_y (int y) { this->y = y; }
    int get_x() const { //修改处
        get_x_cnt += 1;
        return this->x; 
    }
    int get_y() const { return this->y; }//修改处
    int x_cnt() const {//修改处
        return get_x_cnt;
    }
    private :
    //现在get_x_cnt,表示当前对象对get_x函数的调用次数
    //mutable关键字表示不收const关键之的限制
    //所以就算有const关键字,那么mutable声明的变量也可以进行修改
    mutable int get_x_cnt;
    int x, y;
};




int main() {
    const Point p(10, 15), p2(1, 2);
    cout << p.get_x() << ", " << p.get_y() << endl;
    p2.get_x();
    p2.get_x();
    cout << "p1 : get_x_cnt = " << p.x_cnt() << endl;
    cout << "p2 : get_x_cnt = " << p2.x_cnt() << endl;
    return 0;
}

类的声明和定义

还是对类属性和类方法的那份代码进行修改:

#include<iostream>
using namespace std;

//在类中只放方法的声明
class Point {
    public :
    Point();
    Point(int x, int y);
    void set_x (int x);
    void set_y (int y);
    int get_x() const;
    int get_y() const;
    static int x_cnt();
    private :
    static int get_x_cnt;
    int x, y;
};

//对于类中的方法进行实现
//那么就可以将这两部分拆开
//定义放在源文件中,声明放在头文件中
Point::Point() : x(0), y(0) {}
Point::Point(int x, int y) : x(x), y(y) {}
void Point::set_x(int x) {
    this->x = x; 
}

void Point::set_y(int y) {
    this->y = y; 
}

int Point::get_x() const{
    Point::get_x_cnt += 1;
    return x; 
}

int Point::get_y() const{
    return y;
}

int Point::x_cnt() {
    return Point::get_x_cnt; 
}

int Point::get_x_cnt = 0;

int main() {
    Point p1(3, 4), p2(5, 6);
    cout << p1.get_x() << ", " << p1.get_y() << endl;
    cout << p2.get_x() << ", " << p2.get_y() << endl;
    p1.get_x();
    p1.get_x();
    p1.get_x();
    cout << "x_cnt : " << Point::x_cnt() << endl;
    return 0;
}

default和delete关键字

    delete和default关键字引用代码:

#include<iostream>
using namespace std;

class A {
public :
    //A() = delete;
    A() {
        cout << "default constructor" << endl;
    }
    A(int n) : x(n) {
        cout << "conversion constructor" << endl;
    }
    A(const A &) {
        cout << "copy constructor" << endl;
    }
private :
    int x;
};

class B {
public :
    B() = default;
    B(const B&) = default;
private :
    A a1, a2, a3;
};

class C {
public :
    C() = default;
    //C(const C&) {}//这种情况叫撒比构造
    C(const C&) = default;
private :
    A a1, a2, a3;
};

class Point {
public :
    Point() = default;//请求编译器生成默认构造函数
    Point(int x, int y) : x(x), y(y) , p(new int(5)), a(1){}
    //Point(const Point &) = delete;//删除编译器的拷贝构造函数
    //Point(const Point &) = default;//拷贝函数生成默认行为,在使用这个拷贝构造函数时就会造成双重释放的报错
    //这就是深拷贝
    Point(const Point &a) : x(a.x + 1), y(a.y + 2), a(a.a){
        //如果这里成员属性a直接用这样去赋值拷贝
        //那么成员属性a就会使用A类的默认构造函数进行构造
        //而A类的默认构造函数已经被删除了,那么就会报错
        //this->a = a.a;
        this->p = new int;
        *(this->p) = *(a.p);
    }
    ~Point() {
        delete p;
    }
private :
    A a;
    int *p;
    int x, y;
};


int main() {
    Point p1(1, 2), p2 = p1;
    return 0;
}

 对象和引用

        

代码演示:

#include<iostream>
using namespace std;

class A {
public :
    A() = default;
    A(const A&) = default;
private :

};

class B {
public :
    B(A &a) : a(a) {}
private :
    A &a;
};

class C {
public :
    C()  = default;
    C(const C &) = default;
    static void destroy(C *c) {
        delete c; 
        return ;
    }
private :
    ~C() {}
};

int main() {
    A a;
    B b(a);
    C *c = new C();
    C::destroy(c);
    return 0;
}

返回值优化

先回顾一下一个对象是如何进行创建的:

如下代码:

#include<iostream>
using namespace std;

class A {
public :
    A() {
        cout << this << " default constructor" << endl;
    }
    A(const A &) {
        cout << this << " copy constructor" << endl;
    }
    string s;
};

A func() {
    //这里创建temp调用了默认构造
    A temp;
    cout << "temp = " << &temp << endl;
    temp.s = "hello func";
    //return这里会有一个匿名对象
    //然后temp会通过拷贝构造拷贝给这个匿名对象
    return temp;
}

int main() {
    //然后这里的a通过拷贝构造,拷贝这个匿名对象
    //那么创建a对象最终会调用
    //一次默认构造,两次拷贝构造对吧
    A a = func();
    cout << "a = " << &a << endl;
    return 0;
}

执行结果:

会发现结果只调用了一次默认构造,也就是在调用func函数时创建temp进行调用的默认构造,这就就是编译器进行了返回值优化,可以看到对象a的地址和对象的temp的地址一摸一样都没变,说明中间的这些拷贝过程就是浪费资源。

那么我在编译时关闭了,返回值优化,再看结果

和我对于代码的分析是一样的。

总结:

        对于上面的内容,只是我们在程序设计时需要掌握的知识点,应为没有掌握这些知识点,你也无法去实现你对应思想的代码。

        最后来说一下封装,这篇文章最主要的内容就是封装一个类,在下一篇文章C++类中的重载也是封装的一部分。而封装是什么,在设计代码的过程中,我们回去思考一个类他应该有什么属性,什么行为,然后我们就可以将这些属性和行为封装成为一个类。 对象是在程序运行中主要的逻辑,而类就是用来将对象进行分类的。不同的对象对于不同的类,就有不同的属性和行为,而这种代码设计逻辑就是封装。

        总的来说对于封装,可以理解为现实世界中的人和其他物种,我们具体的每个人就是对象,而人就想当于类,而类就去定义了我们的属性和行为,而不同的人有不同的行为和属性,那就是对象。人类就相当于封装了我们的属性和行为。所以在设计一个类去封装时我们需要考虑的就是他有什么属性,什么行为。

下个内容:C++笔记:C++中的重载

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue2知识点————(父子通信)

vue2的知识点&#xff0c;更多前端知识在主页&#xff0c;还有其他知识会持续更新 vue组件 在Vue.js 2.x中&#xff0c;父子组件之间的通信是非常常见的情况&#xff0c;Vue提供了多种方法来实现这种通信。 Props 父向子通信 Props 是父组件向子组件传递数据的一种方式。通过…

Java的八大基本数据类型和 println 的介绍

前言 如果你有C语言的基础&#xff0c;这部分内容就会很简单&#xff0c;但是会有所不同~~ 这是我将要提到的八大基本数据类型&#xff1a; 注意&#xff0c;Java的数据类型是有符号的&#xff01;&#xff01;&#xff01;和C语言不同&#xff0c;Java不存在无符号的数据。 整…

【电控笔记5.8】数字滤波器设计流程频域特性

数字滤波器设计流程&频域特性 2HZ : w=2pi2=12.56 wc=2*pi*5; Ts=0.001; tf_lpf =

【行为型模式】解释器模式

一、解释器模式概述 解释器模式定义&#xff1a;给分析对象定义一个语言&#xff0c;并定义该语言的文法表示&#xff0c;再设计一个解析器来解释语言中的句子。也就是说&#xff0c;用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口&#xff0c;该接口…

设计模式——状态模式19

状态模式是一种行为设计模式&#xff0c; 允许一个对象在其内部状态改变时改变它的行为&#xff0c;对象看起来好像修改了它的类。状态模式的核心是状态与行为绑定&#xff0c;不同的状态对应不同的行为。 设计模式&#xff0c;一定要敲代码理解 状态行为抽象 //在某种状态下&…

数据库——实 验 8 SQL 编程

1.T-SQL 语言简介 SQL Server 使用的语言称作 Transact-SQL, 它不仅包括基本 SQL 操作的内容&#xff0c;如 SQL 的数据查询功能和数据操作功能等&#xff0c;还有一般程序设计的能力。 2. 局部变量和全局变量的概念 1)局部变量 局部变量是一个能够拥有特定数据类型的对…

多目标应用:基于非支配排序粒子群优化算法NSPSO求解无人机三维路径规划(MATLAB代码)

一、无人机多目标优化模型 无人机三维路径规划是无人机在执行任务过程中的非常关键的环节&#xff0c;无人机三维路径规划的主要目的是在满足任务需求和自主飞行约束的基础上&#xff0c;计算出发点和目标点之间的最佳航路。 1.1路径成本 无人机三维路径规划的首要目标是寻找…

html网页在展示时,监听网络是否断网,如果断网页面暂停点击响应

序言&#xff1a; 集合百家之所长&#xff0c;方著此篇文章&#xff0c;废话少说&#xff0c;直接上代码&#xff0c;找好你的测试网页&#xff0c;进行配置&#xff0c;然后复制粘贴代码&#xff0c;就可以了。 1.css文件内容 #newbody{display: none;width: 100%;height: 9…

【用户投稿】Apache SeaTunnel 2.3.3+Web 1.0.0版本安装部署

项目概要 Apache SeaTunnel 是一个分布式、高性能、易扩展的数据集成平台&#xff0c;用于实时和离线数据处理,支持多种数据源之间的数据迁移和转换。 其中&#xff0c;Apache-seatunnel-web-1.0.0-bin.tar.gz和apache-seatunnel-2.3.3-bin.tar.gz代表了 Apache SeaTunnel Web…

python语言实现语音合成(文字转语音)

python语言实现语音合成&#xff08;文字转语音&#xff09; 在Python中实现文本到语音——语音朗读功能&#xff0c;可以使用pyttsx3库。pyttsx3库的安装和使用也相对简单&#xff0c;但在控制语音的暂停、继续和停止功能方面可能存在一定的困难。 首先&#xff0c;您需要安装…

北航计算机软件技术基础课程作业笔记【4】

题目&#xff08;好像以前没加&#xff09; 二叉树与哈希表 作业 1.二叉树前序遍历结果 二叉树结构为 代码实现中序后序推理前序表达式 #include <iostream> #include <stack> #include <string> #include <vector> #include <deque> ​ // …

H800算力低至5.99元/卡时!抢鲜体验LLaMA3最佳实践就在潞晨云

由Meta发布的LLaMA3 8B和LLaMA3 70B的&#xff0c;将开源AI大模型推向新的高度。在多个基准测试上的表现均大幅超过已有竞品&#xff0c;成为AI应用的最新优选。 潞晨云现已上架 LLaMA3 8B和LLaMA3 70B从推理到微调和预训练的实践教程。 提供免费测试代金券&#xff0c;限时特…

yolov8 区域多类别计数

yolov8 区域多类别计数 1. 基础2. 计数功能2.1 计数模块2.2 判断模块 3. 初始代码4. 实验结果5. 完整代码6. 源码 1. 基础 本项目是在 WindowsYOLOV8环境配置 的基础上实现的&#xff0c;测距原理可见上边文章 2. 计数功能 2.1 计数模块 在指定区域内计数模块 region_point…

附近商户-GEO数据结构的基本用法

10、附近商户 10.1、附近商户-GEO数据结构的基本用法 GEO就是Geolocation的简写形式&#xff0c;代表地理坐标。Redis在3.2版本中加入了对GEO的支持&#xff0c;允许存储地理坐标信息&#xff0c;帮助我们根据经纬度来检索数据。常见的命令有&#xff1a; GEOADD&#xff1a…

Docker的介绍及应用

1.什么是Docker 我们在部署大型项目的时候&#xff0c;肯定会遇到这种问题&#xff0c;大学项目组件较多&#xff0c;运行环境复杂&#xff0c;部署时会碰到一些问题&#xff1a;例如node、redis、mysql等这些应用都有自己的依赖和函数库。这种复杂的依赖关系很容易出现兼容问…

【GitHub】github学生认证,使用copilot教程

github学生认证并使用copilot教程 写在最前面一.注册github账号1.1、注册1.2、完善你的profile 二、Github 学生认证 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持 ~ &#x1f680; 欢迎一起踏上探险之旅&a…

Python PyTorch 获取 MNIST 数据

Python PyTorch 获取 MNIST 数据 1 PyTorch 获取 MNIST 数据2 PyTorch 保存 MNIST 数据3 PyTorch 显示 MNIST 数据 1 PyTorch 获取 MNIST 数据 import torch import numpy as np import matplotlib.pyplot as plt # type: ignore from torchvision import datasets, transform…

如何修复U盘在Windows 10上断开又重新连接的问题?这里有方法

序言 有时,当你把U盘连接到电脑上时,U盘每隔几秒钟就会断开连接并重新连接,这导致你无法正常复制和传输文件,这真的很烦人。硬件或驱动程序可能有问题。 在这种情况下,你需要确保此U盘与其他计算机是否正常工作。如果是,则表示你的驱动器没有问题。如果不是,不要担心。…

基于RK3588的全国产鸿蒙边缘计算工控机在智能交通ETC收费系统的应用

1.1 产品简介 基于智能交通、工业互联等行业快速智能化发展的需求&#xff0c;以 OpenHarmony 为框架开发嵌入 HamonyOS&#xff0c;打造了具有高智能、高可靠、高安全的自主 可控的边缘处理器 XM-RK3588。 图 1-1 边缘处理器 HamonyOS强化 IoT 互联互动能力&#xff0c;让边缘…