【与C++的邂逅】--- 类和对象(上)

 Welcome to 9ilk's Code World

       

(๑•́ ₃ •̀๑) 个人主页:        9ilk

(๑•́ ₃ •̀๑) 文章专栏:     与C++的邂逅


本篇博客将讲解C++中的类和对象,C++是面向对象的语言,面向对象三大特性是封装,继承,多态。学习类和对象,我们可以很好的认识到封装这一层。

本篇内容思维导图:


🏠 面向过程和面向对象初步认识

  1. C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。面向对象即将一件事分为好几个过程。
  2. C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。面向对象即关注对象以及对象之间的互动。
    上图中总共有 四个对象:人,衣服,洗衣粉,洗衣机。
    整个洗衣服过程主要是: 人,衣服、洗衣粉、洗衣机四个对象之间交互完成的,人不需要关
    新洗衣机具体是如何洗衣服的,是如何甩干的。

🏠 类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,
会发现struct中也可以定义函数。
typedef int DataType;
struct Stack
{DataType Top(){return _array[_size - 1];}DataType* _array;size_t _capacity;size_t _size;
};

C++中的struct与C语言中的不同:

  • C++中struct兼容了C语言中struct的用法,但同时将它升级成了类。
  • struct在C++中可以定义变量(成员变量),也可以定义函数(成员函数),且定义的函数不需要传参就可以使用在struct中定义的变量。
  • 升级之后的类可以直接用struct名称来代表类型。一个类对应一个自定义类型,那么他就可以创建出N个变量,也就是能实例化出N个对象。
struct ListNode
{int _data;
}struct List
{ListNode* next; //兼容之前struct
}int main()
{struct List l1; List l2; //直接用struct名称定义对象 return 0;
}

但是上面结构体的定义,C++中还是喜欢用class来代替。

🏠 类的定义

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号
  • class为定义类的限定符,className为类名,{ }中为类的主体,注意定义类结束时后面分号不能省略。
  • 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数

📌 成员变量命名规则的建议

class Date
{
public:void Init(int year){// 这里的year到底是成员变量,还是函数形参?year = year;}
private:int year;
};

这段代码是可以正常编译的,但是定义的成员变量不能初始化成功,因为编译器不知道这里的year到底是成员变量还是函数形参。我们建议将成员变量采用一些前缀命名来以示区分。

class Date
{
public:void Init(int year){_year = year;}
private:int _year;
};// 或者这样
class Date
{
public:void Init(int year){mYear = year;}
private:int mYear;
};

📌 类的两种定义方式:

  • 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内
    联函数处理。
class Person
{public:
//显示基本信息
void showlnfo()
{cout <<_name<< "-" <<_sex << "-" << _age << endl;
}public:char* _name;char* _sex;int _age;
};
  • 类声明放在 .h 文件中,成员函数定义放在 .cpp 文件中,注意: 成员函数名前需要加类名 :: .
// Person.cpp
class Person
{public:
//显示基本信息
void showlnfo(); //声明
public:char* _name;char* _sex;int _age;
};//Person.h
void Person::showlnfo()
{cout <<_name<< "-" <<_sex << "-" << _age << endl;
}

在这里类形成了一个类域,在.h文件时,先局部域再全局域搜索showlnfo,此时两个域都没有找到这个函数,因此只有指定类域在类域里面找才能找到。

🏠 类的访问限定符及封装

📌 访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过 访问权限选择性的 将其接口提供给外部的用户使用。
访问限定符说明:
  • public修饰的成员在类外可以直接被访问,protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的).
class A
{
public:void f(){cout << "f()" << endl; }
private:int _a;
}int main()
{A aa;aa.f(); //成员函数是公有可以在类外直接访问cout << aa._a << endl; //错误 成员函数是私有在类外不能直接访问。return 0;
}
  • 虽然被private修饰的成员不能在类外直接访问,但是能在类外通过其他公有成员间接访问
class A
{
public:void print(){cout << _a << endl; }
private:int _a;
}int main()
{A aa;cout << aa._a << endl;return 0;
}
  • class的默认访问权限为private,struct为public(因为struct要兼容C)
class A
{int _a; //此时a是被private修饰的
}struct B
{int _b; //此时b是被public修饰的
}
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止;如果后面没有访问限定符,作用域就到 } 即类结束。
class Date
{//默认就是private 此时Init在类外不能直接访问void Init(int year){_year = year;}
public:void func() //此时public作用域从它出现位置到private{}private:int _year;
};
  • C++类中常将成员变量的访问权限设置为private,成员函数设置为public,此时成员变量经过封装只能通过公共渠道也就是成员函数访问,保证了数据的安全性。
  • 访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别,在语法层上限制你对成员的使用而不是影响成员所在位置
【面试题】 问题: C++ struct class 的区别是什么?
1.C++需要兼容C语言,所以C++中struct可以当成结构体使用。
2.C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
3.在继承和模板参数列表位置,struct和class也有区别。

📌 封装

【面试题】 面向对象的三大特性:封装、继承、多态 。 在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
  • 封装本质上是一种管理,让用户更方便使用类。在类这里,将数据和方法放进类里这是第一层封装;类里面又使用了访问限定符这是第二层封装。
例子:比如西安的景点兵马俑并不是随意让游客触摸的,是有着一定保护的,如果没有保护就相当于是C语言结构体成员直接放开,用围栏围住相当于是将兵马俑(成员)保护起来,方便进行管理。
  • 在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

🏠 类的作用域

类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。类域是为了防止类域中与其他域同名成员产生冲突。
class Person
{
public:void PrintPersonInfo();
private:char _name[20];char _gender[3];int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{cout << _name << " "<< _gender << " " << _age << endl;
}

🏠 类的实例化

用类类型创建对象的过程,称为类的实例化.
  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;就像C语言我们定义这个结构体类型时,只是对他的一个声明,实际并未开空间。
  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量。就像C语言中我们用结构体类型定义变量时内存是实际分配物理空间给这个变量的。
  3. 类和对象是一对多的关系。类好比现实中工程师设计的建筑图纸,而对象类似根据图纸建造出来的建筑(是实际分配空间的)。

🏠 类的对象模型

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算
一个类的大小?

📌 类对象的存储方式猜测

  • 对象中包含类的各个成员

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一
个类创建多个对象时, 每个对象中都会保存一份代码,相同代码保存多次,浪费空间。
  • 代码只保存一份,在对象中保存存放代码的地址
  • 只保存成员变量,成员函数存放在公共的代码段
对于上述三种存储方式,那计算机到底是按照那种方式来存储的?

📌计算类的大小

// 类中既有成员变量,又有成员函数
class A1 
{
public:void f1(){}
private:int _a;
};// 类中仅有成员函数
class A2 
{
public:void f2() {}
};// 类中什么都没有---空类
class A3
{};int main()
{cout << sizeof(A1) << endl;cout << sizeof(A2) << endl;cout << sizeof(A3) << endl;return 0;
}

注:既可以sizeof()实例化出来的对象,也可以sizeof()类,好比我们可以根据设计图纸推测出建造所需要的空间。

输出结果:

4  //A1

1  //A2

1  //A3

由此我们可以得到以下结论:

  • 一个类的大小,实际就是该类中 成员变量 之和,当然要注意内存对齐
  • 类的对象模型应该对应的是猜测三,也就是 成员函数放在公共代码段,因为如果每个对象放一份会造成大大的浪费。
  • 对于空类占一个字节,这一个字节不存储有效数据,而是用来标识对象被定义出来了。
  • 对于嵌套类类比嵌套结构体
class A1
{
public:char _c;int _a; 
};//对齐到8class A2
{
public:long long _l; //8A1 _aa;//_aa最大对齐数为最大成员也就是int的对齐数,大小是这个类大小
};int main()
{cout << sizeof(A1) << endl; //8cout << sizeof(A2); //16return 0;
}

📌 结构体内存对齐规则

1. 第一个成员在与结构体偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处.注意:对齐数 = 编译器默认的一个对齐数与该成员大小的较小值.VS中默认的对齐数为 8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己成员中最大对齐数的整数倍处,所占大小就是这个嵌套结构体大小,最后结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?
答:结构体对齐规则如上。
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的 ;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问; ⽽对⻬的内存访问仅需要⼀次访问 。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以 ⽤⼀个内存操作来读或者写值 了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总的来说,内存对齐是以空间换时间的做法。
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照 3 4 5 即任意字节对齐?
1.#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
2.若进行任意对齐,由于大多数处理器每次从内存读取2的倍数个字节,此时若不是2的倍数,数据一多起来就有可能降低效率。因此不能按任意字节对齐。
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
1.超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分为⼤端字节序存储和⼩端字节序存储。
a.⼤端(存储)模式:是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。
b. ⼩端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存 在内存的⾼地址处。
2.  对于位数⼤于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度⼤ 于⼀个字节,那么必然存在着⼀个如何将多个字节安排的问题,此时需要考虑大小端。

🏠 类成员函数的this指针

📌 this指针的引入

class Date
{ 
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout <<_year<< "-" <<_month << "-"<< _day <<endl;}private:int _year;     // 年int _month;    // 月int _day;      // 日
};int main()
{Date d1, d2;d1.Init(2022,1,11);d2.Init(2022, 1, 12);d1.Print();d2.Print();return 0;
}

对于上述这样的一个日期类,用Date类实例化出来d1和d2两个对象,Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用Init函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?C++中通过引入this指针来解决这个问题.

C++ 编译器给每个 非静态的成员函数 增加了一个 隐藏的指针参数 ,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 成员变量 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递, 编译器自动完成

📌 this指针的特性

  • this指针的类型时 类类型 * const,即this指针不能被赋值,但是能被强转.
class A1
{
public:void Print(){//this = nullptr;  //不能被赋值cout << typeid(this).name() << endl;//cout <<(A1*)this->_a << endl;cout << typeid((A1*)this).name();}char _c;int _a; 
};
  • this指针只能在“成员函数”的内部使用.
  • this指针本质是"成员函数"的形参,当对象调用成员函数时,会将对象的地址作为实参传给this形参,this指针在形参和实参不能显示写由编译器处理.
 void Print(){cout << _a << endl;}//编译器处理
void Print(Date* const this)
{cout << this->_a <<endl;
}
  • this 指针是 成员函数 第一个隐含的指针形参,一般情况由编译器通过 ecx 寄存器自动传
    递,不需要用户传递.
    // 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
    class A
    {
    public:void Print(){cout << "Print()" << endl;}
    private:int _a;
    };
    int main()
    {A* p = nullptr;p->Print();return 0;
    }// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
    class A
    { 
    public:void PrintA() {cout<<_a<<endl;}
    private:int _a;
    };
    int main()
    {A* p = nullptr;p->PrintA();return 0;
    }

    对于上面这两段代码,都是将A类型指针赋值为nullptr,当p->PrintA时,并不是解引用空指针,本质是将nullptr传递给成员函数的形参this指针,而且成员函数并未存在对象里,,所以对于代码一能正常运行;对于代码二访问了this指针指向内容此时是空指针解引用因此会崩溃.值得注意的是,如果解引用空指针并不会编译报错,因为不是语法层的错误.

    我们从汇编层就可以看到问题所在。

    【面试题】 
    1. this 指针存在哪里?
    this指针本质是成员函数的形参,我们知道函数形参是存在栈的,因此this指针存在栈区,但是有的编译器会优化直接存在ecx寄存器里面。但是注意的是,this指针不可能存在对象里,空类大小为1就可以证明。
    2. this 指针可以为空吗?
    this指针可以为空,但是注意空指针的解引用问题,而且这里是对象指针初始化为空,并不意味着接下来可以给他赋值为空。

    本节我们初步认识了类中的成员们以及一些细节比如访问限定符,this指针等,夏姐我们将讲解有关类的默认成员函数。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C语言】深入讲解指针(中)

文章目录 前言函数指针函数指针变量的创建函数指针变量的使用两段有趣的代码typedef 关键字 函数指针数组函数指针的使用最后 前言 上一章深入讲解指针&#xff08;上&#xff09;我们对字符指针、数组指针、指针和数组传参进行了讲解&#xff0c;本章将对函数指针进行讲解&am…

【Python】标准库的使用

文章目录 标准库日期计算字符串操作剑指offer 58&#xff0c;翻转单词顺序思路 leetcode 796&#xff0c;旋转字符串思路 leetcode 2255&#xff0c;统计是给定字符串前缀的字符串数目思路 文件查找工具 Python 通过模块来体现“库” 降低了程序猿的学习成本提高了程序的开发效…

【C语言篇】编译和链接以及预处理介绍(下篇)

文章目录 前言#和###运算符##运算符 命名约定#undef命令⾏定义条件编译#if和#endif多个分支的条件编译判断是否被定义嵌套指令 头文件被包含头文件被包含的方式本地文件包含库文件的包含 嵌套文件包含 其他预处理指令 写在最后 前言 本篇接前一篇【C语言篇】编译和链接以及预处…

【LeetCode】每日一题 2024_9_16 公交站间的距离(模拟)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动&#xff01; 题目&#xff1a;公交站间的距离 代码与解题思路 func distanceBetweenBusStops(distance []int, start int, destination int) int {// 首先让 start > destination, 这两个谁大对结果没有影响&#…

免费爬虫软件“HyperlinkCollector超链采集器v0.1”

HyperlinkCollector超链采集器单机版v0.1 软件采用python的pyside2和selenium开发,暂时只支持window环境&#xff0c;抓取方式支持普通程序抓取和selenium模拟浏览器抓取。软件遵守robots协议。 首先下载后解压缩&#xff0c;然后运行app目录下的HyperlinkCollector.exe 运行…

C语言——rand函数

一、rand函数 这是一个在 C 标准库 <stdlib.h> 中定义的函数&#xff0c;用于生成伪随机数&#xff0c;默认情况下&#xff0c;它生成从 0 到 RAND_MAX 的伪随机数&#xff0c;其中 RAND_MAX 是一个常数&#xff0c;通常是 32767。 1、函数原型&#xff1a; 2、函数返回…

【数据结构】排序算法---直接插入排序

文章目录 1. 定义2. 算法步骤3. 动图演示4. 性质5. 算法分析6. 代码实现C语言PythonJavaCGo 7. 折半插入排序代码实现——C 结语 1. 定义 直接插入排序是一种简单直观的排序算法。它的工作原理为将待排列元素划分为「已排序」和「未排序」两部分&#xff0c;每次从「未排序的」…

C++ char*和char[] 可能指向的内存区域详解(附实验)

C char* 指向的内存区域详解 写在前面c内存结构简介指针常量和常量指针简介情况一&#xff1a;char* 指向栈区内容情况二&#xff1a;char* 指向堆区内容情况三&#xff1a;char* 指向常量区内容情况四&#xff1a;char* 指向静态区内容情况五&#xff1a;char* 指向全局区内容…

SQL题目分析:打折日期交叉问题--计算品牌总优惠天数

在电商平台的数据分析中&#xff0c;处理品牌促销活动的日期交叉问题是一个挑战。本文将介绍几种高级SQL技巧&#xff0c;用于准确计算每个品牌的总优惠天数&#xff0c;即使在存在日期交叉的情况下。 问题背景 我们有一个促销活动表 shop_discount&#xff0c;记录了不同品牌…

docker-compose 部署 flink

下载 flink 镜像 [rootlocalhost ~]# docker pull flink Using default tag: latest latest: Pulling from library/flink 762bedf4b1b7: Pull complete 95f9bd9906fa: Pull complete a880dee0d8e9: Pull complete 8c5deab9cbd6: Pull complete 56c142282fae: Pull comple…

【二叉树进阶】二叉搜索树

目录 1. 二叉搜索树概念 2. 二叉搜索树的实现 2.1 创建二叉搜索树节点 2.2 创建实现二叉搜索树 2.3 二叉搜索树的查找 2.4 二叉搜索树的插入 2.5 二叉搜索树的删除 2.6 中序遍历 2.7 完整代码加测试 3. 二叉搜索树的应用 3.1 K模型&#xff1a; 3.2 KV模型&#xf…

【C++11】可变参数模板

【C11】可变参数模板 一、可变参数模板概念以及定义方式 ​ 在C11之前&#xff0c;类模板和函数模板只能含有固定数量的模板参数&#xff0c;c11增加了可变模板参数特性&#xff1a;允许模板定义中包含0到任意个模板参数。声明可变参数模板时&#xff0c;需要在typename或cla…

数据在内存中的存储方式

前言&#xff1a;已经好久没更新了&#xff0c;开学之后学习编程的时间少了很多。因此&#xff0c;已经好几个礼拜都没有写文章了。 在讲解操作符的时候&#xff0c;我们就已经学习过了整数在内存中的存储方式。若有不懂的伙伴可以前往操作符详解进行学习。今天我们主要来学习…

[数据集][目标检测]智慧交通铁路人员危险行为躺站坐检测数据集VOC+YOLO格式3766张4类别

图片数量(jpg文件个数)&#xff1a;3766 标注数量(xml文件个数)&#xff1a;3766 标注数量(txt文件个数)&#xff1a;3766 标注类别数&#xff1a;4 标注类别名称:["sitting","sleeping","standing","track"] 每个类别标注的框数&…

NC 矩阵最长递增路径

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个 n 行…

<Linux> 进程间通信

目录 一、进程间通信介绍 1. 进程间通信概念 2. 进程间通信目的 3. 进程间通信的本质 4. 进程间通信发展 5. 进程间通信分类 管道&#xff08;文件缓冲区&#xff09; System V IPC POSIX IPC 二、管道 1. 匿名管道 1.1 匿名管道原理 1.2 pipe系统调用 1.3 匿名管道的使用 1.4…

Java项目基于docker 部署配置

linux新建文件夹 data cd datatouch Dockerfilesudo vim Dockerfile# 使用一个基础的 Java 镜像&#xff08;根据自己项目中使用的是什么jdk版本设置&#xff0c;用于拉取执行jar包的jdk环境&#xff09; FROM openjdk:8# 指定工作目录 VOLUME /data# 复制应用程序的 JAR 文件…

超详解——​深入理解Python中的位运算与常用内置函数/模块——基础篇

目录 ​编辑 1.位运算 2.常用内置函数/模块 math模块 random模块 decimal模块 常用内置函数 3.深入理解和应用 位运算的实际应用 1.权限管理 2.位图 3.图像处理 2.math模块的高级应用 统计计算 几何计算 总结 1.位运算 位运算是对整数在内存中的二进制表示进行…

nginx负载均衡(轮询与权重)

文章目录 1. nginx的介绍2. nginx使用场景3. nginx在windows的下载与安装4. nginx的简单使用5. nginx进行轮询测试6. nginx进行权重测试7. 总结 1. nginx的介绍 Nginx&#xff08;engine x&#xff09;是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也是一个开源的、…

CSS 响应式设计(补充)——WEB开发系列36

随着移动设备的普及&#xff0c;网页设计的焦点逐渐转向了响应式设计。响应式设计不仅要求网页在各种屏幕尺寸上良好展示&#xff0c;还要适应不同设备的特性。 一、响应式设计之前的灵活布局 在响应式设计流行之前&#xff0c;网页布局通常是固定的或流动的。固定布局使用固定…