【C++】面向对象 | 类详解 | this指针

目录

1. 面向过程和面向对象

2. 类的引入

3. 类的定义

4. 类的访问限定符及封装

4.1 访问限定符

4.2 封装

5. 类的作用域

6. 类的实例化

7. 类对象模型

7.1 如何计算类对象的大小

7.2 类对象的存储方式猜测

7.3 结构体内存对齐规则

8. this指针

8.1 this指针的引出

8.2 this指针的特性

8.3 this指针存在哪里?

8.4 判断下列代码理解this指针


1. 面向过程和面向对象

C语言:是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。C++:是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。

  • 什么是对象?
    对象是一个实体,我们眼睛看到的所有实体都可以看成一个实体对象。
  • 什么是类?
    类是用来对实体(对象)进行描述的。(对象有什么属性,有什么功能)类是一种自定义类型。

2. 类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数。

typedef int DataType;
struct Stack
{
    void Init(size_t capacity)
    {
        _array = (DataType*)malloc(sizeof(DataType) * capacity); 
        if (nullptr == _array)
        {
            perror("malloc申请空间失败");
            return;
        }

     _capacity = capacity;
     _size = 0;
}

void Push(const DataType& data)
{
    // 扩容
    _array[_size] = data;
    ++_size;
}                            

DataType Top()
{
    return _array[_size - 1]; }
    void Destroy()
{
    if (_array)
    {
        free(_array);   
         _array = nullptr; 
        _capacity = 0;   
        _size = 0;
    }
}

    DataType* _array;
    size_t _capacity;
    size_t _size;
};

int main()
{
    Stack s;
    s.Init(10);
    s.Push(1);
    s.Push(2);
    s.Push(3);
    cout << s.Top() << endl; 
    s.Destroy();
    return 0;
}

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


3. 类的定义

class className
{
    // 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

class为定义类的关键字,ClassName为类的名字,{ } 中为类的主体,注意类定义结束时后面分号不能省略。

  • 类体中内容称为类的成员;
  • 类中的变量称为类的属性或成员变量;
  • 类中的函数称为类的方法或者成员函数。

类的两种定义方式:
1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名 ::

一般情况下,更期望采用第二种方式。  
注意:上课为了方便演示使用方式一定义类,大家后序工作中尽量使用第二种。

成员变量命名规则:

class Date
{
public:
	void Init(int year,int mouth,int day)
	{
		_year = year;
		_mouth = mouth;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _mouth << "-" << _day << endl; 
	}
private:
	int _year;
	int _mouth;
	int _day;
};

int main()
{
	Date d1;
	d1.Init(2024,3,28);
	d1.print();
	
	return 0;
}

或者声明和定义分离:

// data.h文件
class Date
{
public:
	void Init(int year,int mouth,int day);
	void print();
	
private:
	int _year;
	int _mouth;
	int _day;
};


// data.cpp文件
void Date :: Init(int year,int mouth,int day)
	{
		_year = year;
		_mouth = mouth;
		_day = day;
	}
void Date :: print()
	{
		cout << _year << "-" << _mouth << "-" << _day << endl; 
	}

// test.cpp文件
int main()
{
	Date d1;
	d1.Init(2024,3,28);
	d1.print();
	
	return 0;
}

4. 类的访问限定符及封装

4.1 访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用。

【访问限定符说明】

1. public修饰的成员在类外可以直接被访问。

2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。

3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。

4. 如果后面没有访问限定符,作用域就到 } 即类结束。

5. class的默认访问权限为private,struct为public(因为struct要兼容C)。

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别 。

问题:C++中struct和class的区别是什么?
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private

注意:在继承和模板参数列表位置,  struct和class也有区别,后序给大家介绍。

4.2 封装

面向对象的三大特性:封装、继承、多态。

封装:将数据操作数据的方法放到类里面进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

封装的意义:
(1)保护类中的成员,不让类以外的程序直接访问或修改,只能通过提供的公共接口访问(数据封装)。
(2)隐藏方法,只要接口不变,内容的修改不会影响到外部的调用者(方法封装)。
(3)封装可以使对象拥有完整的属性和方法(类中的函数)。

继承:主要实现重用代码,节省开发时间,新创建的类就可直接复用之前类中的所有成员。

多态: 指不同对象接收到同一消息时会产生不同的行为(一个接口,多种方法),简单来说,就是在同一个类或继承体系结构的基类与派生类中,用同名函数来实现各种不同的功能。


5. 类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。  在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。

class Person
{
public:
    void PrintPersonInfo();
private:
    char _name[20];
    char _gender[3];
    int  _age;
};

// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
    cout << _name << " "<< _gender << " " << _age << endl;
}

6. 类的实例化

用类创建对象的过程,称为类的实例化。
1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。
2.
一个类可以实例化出多个对象,  实例化出的对象占用实际的物理空间,存储类成员变量。

int main()
{
    Person._age = 100;   // 编译失败: error C2059: 语法错误:“.”
    return 0;
}

Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。

3. 做个比方: 类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象 才能实际存储数据,占用物理空间。


7. 类对象模型

7.1 如何计算类对象的大小
class A
{
public:
    void PrintA()
    {
        cout<<_a<<endl;
    }
private:
    char _a;
};

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

7.2 类对象的存储方式猜测

(1)如果包含对象中所有成员:成员变量+需要调用的函数。
缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一 个类创建多个对象时,  每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么 如何解决呢?

(2)代码只保存一份,在对象中保存存放代码的地址。

(3)只保存成员变量,成员函数存放在公共的代码段。

问题:对于上述三种存储方式,那计算机到底是按照那种方式来存储的?
        我们再通过对下面的不同对象分别获取大小来分析看下:

// 类中既有成员变量,又有成员函数
class A1 
{
public:
    void f1(){}
private:
    int _a;
};
// 类中仅有成员函数
class A2 
{
public:
    void f2() {}
};

// 类中什么都没有---空类
class A3
{};

sizeof(A1) : __4____                sizeof(A2) : ____1__                     sizeof(A3) : ___1___

结论:一个类的大小,实际就是该类中”成员变量”之和当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象

7.3 结构体内存对齐规则

为什么要内存对齐
(1)性能原因:为了处理器对内存访问更加快速便捷,提升性能。
(2)平台原因:某些硬件平台只能在特定地址处访问,否则抛出硬件异常。

1. 第一个成员在与结构体偏移量为0的地址处。

2. 第二个成员变量要对齐到该成员对齐数的整数倍的地址处。

3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

(注意:对齐数 = 编译器默认的一个对齐数与该成员大小的较小值,VS中默认的对齐数为8)


8. this指针

8.1 this指针的引出

我们先来定义一个日期类 Date:

class Date
{
public:
	void Init(int mouth,int day,int year)
	{
		_mouth = year;
		_day = day;
		_year = year;
	}
	void print()
	{
		cout << _mouth << " " << _day << " " << _year << endl;
	}
private:
	int _mouth;
	int _day;
	int _year;
};

int main()
{
	Date d1;
	Date d2;

	d1.Init(2,1,2024);
	d2.Init(2,2,2024);

	d1.print();
	d2.print();

	return 0;
}

输出:   

对于上述类,有这样的一个问题:我们都知道类的成员函数可以访问类的数据(限定符只是限定于类外的一些操作,类内的一切对于成员函数来说都是透明的),那么成员函数如何知道哪个对象的数据成员要被操作呢,原因在于每个对象都拥有一个指针:this指针,通过this指针来访问自己的地址。

8.2 this指针的特性
  1. this指针的类型:类类型*const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用。
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传。
  5. this指针并不是对象的一部分,this指针所占的内存大小是不会反应在sizeof操作符上的。this指针的类型取决于使用this指针的成员函数类型以及对象类型,

在看下面d1作为实参传递给this形参,所以取他们的地址是相同的。

class Date
{
public:
	void Init(int year,int mouth,int day)
	{
		_year = year;
		_mouth = mouth;
		_day = day;
		cout << this << endl;
		cout << " " << endl;
	}
	void print()
	{
		cout << _year << "-" << _mouth << "-" << _day << endl; 
		cout << this << endl;
		cout << " " << endl;
	}
private:
	int _year;
	int _mouth;
	int _day;
};

int main()
{
	Date d1;

	d1.Init(2024,3,28);
	d1.print();

	printf("%p\n",&d1);
	
	return 0;
}
8.3 this指针存在哪里?

答:this指针是个形参,所以跟普通参数一样存在函数调用的栈区里。

8.4 判断下列代码理解this指针

(1)下面的程序的运行结果是?
          A. 编译报错
          B. 运行崩溃
          C. 正常运行

class A
{
public:
	void Show()
	{
		cout << "show()" << endl;
	}
private:
	int _a;
};
 
int main()
{
	A* p = nullptr;
	p->Show();
    (*p).Show(); //这两种情况是一样的
 
	return 0;
}

结果:C
原因:Show()函数是存在公共代码区中,编译的时候在公共代码区中找到这个函数,和普通的函数调用是一样的,只需要call函数地址就行。我们发现这里p是空指针,传过去的this指针只是接收了p的空指针,就类似于this指针被初始化为空指针。这是允许的,调用Show函数的时候没有用到this指针的解引用。

(2)下面的程序的运行结果是?          
          A. 编译报错
          B. 运行崩溃
          C. 正常运行

class B
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
 
	B* p2 = nullptr;
	p2->PrintA();
 
	return 0;
}

结果:B
原因:B* p2 = nullptr; 相当于把this  = nullptr,此程序崩溃是在PrintA()中,会隐含一个this->_a,而this指针是一个空指针,访问this指针_a的位置,就要对空指针进行解引用,此时就会崩溃,
当调用PrintA() 函数的时候,用到了this->_a。


本章完   

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

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

相关文章

基于PyTorch深度学习实战入门系列-(1)环境配置

Pytorch环境安装配置2024最新版 下载安装Anaconda Anaconda下载网址&#xff1a;Free Download | Anaconda 创建虚拟环境 打开Anaconda Prompt # conda create -n 环境名 [需要的库] # 例子&#xff1a; conda create -n pytorchpy39 python3.9安装过程中需要确认输入 y 回车…

Filebeat将csv导入es尝试

一、安装 在docker中安装部署ELKfilebeat 二、主要配置 - type: log # Change to true to enable this input configuration. enabled: true # Paths that should be crawled and fetched. Glob based paths. paths: - /home/centos/pip_v2.csv #源路径 #…

1908 - 伐木工

代码 #include<bits/stdc.h> using namespace std; long long a[1000100],n,m,l1,r,mid,i; bool fm(long long x) {long long s0;for(i1;i<n;i){if(x<a[i]) ssa[i]-x;if(s>m) return true;}return false; } int main() {cin>>n>>m;for(i1;i<n;i…

简易内存池2 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 请实现一个简易内存池,根据请求命令完成内存分配和释放。 内存池支持两种操作命令&#xff0c;REQUEST和RELEASE&#xff0c;其格式为: REQUEST请求的内存大小 …

Day21-磁盘管理之raid及分区

Day21-磁盘管理之raid及分区 1 Raid技术1.1 什么是Raid?1.2 为什么服务器需要Raid?1.3 什么是Raid级别?1.4 Raid有哪些实现方式&#xff1f;1.5 什么是RAID0&#xff1f;&#xff08;图&#xff09;1.6 什么是RAID1&#xff1f;&#xff08;图&#xff09;1.7 什么是RAID5&a…

Python之Flask框架~四大内置对象

1.g global全局对象 g对象是专门用来保存用户的数据的 g对象在一次请求中的所有的代码的地方, 都是可以使用的 突破变量存储位置限制,为数据传递添加了新的方式,比如我们在before_request产生一个数据在后面需要使 用, 可以保存在g对象中, 在其他视图西数中就可以使用这个数据…

LaTeX-设置图像大小

文章目录 LaTeX-设置图像大小1.导入图片2.更改图像大小并旋转图像2.1按比例缩放2.2将图像缩放到特定的宽度和高度2.3将图片设置为与文字宽度相同2.4旋转图片 LaTeX-设置图像大小 在本文中&#xff0c;将解释如何以最常见的格式包含图像&#xff0c;如何缩小、放大和旋转它们。…

Day11:信息打点-Web应用企业产权指纹识别域名资产网络空间威胁情报

目录 Web信息收集工具 业务资产-应用类型分类 Web单域名获取-接口查询 Web子域名获取-解析枚举 Web架构资产-平台指纹识别 思维导图 章节知识点&#xff1a; Web&#xff1a;语言/CMS/中间件/数据库/系统/WAF等 系统&#xff1a;操作系统/端口服务/网络环境/防火墙等 应用…

考研机试C++题目精选

更多内容会在godownio.github.io更新 算法练习&#xff08;C代码&#xff09; 考研上机或C语言代码笔试准备&#xff0c;暨大机试原题letcode牛客中南大等高校机试 快速幂算法 题目&#xff1a;输入一个整数 n &#xff0c;求 n^n 的个位数是多少。 快速幂算法&#xff1a;…

集合篇之ArrayList

一、源码如何分析&#xff1f; 1.成员变量 2.构造方法 3.关键方法 一些添加的方法。 二、debug看源码 我们给出下面代码&#xff1a; public void test01() {ArrayList<Integer> list new ArrayList<>();list.add(1);for (int i 2; i < 10; i) {list.add(i…

Java虚拟机(JVM)从入门到实战【上】

Java虚拟机&#xff08;JVM&#xff09;从入门到实战【上】&#xff0c;涵盖类加载&#xff0c;双亲委派机制&#xff0c;垃圾回收器及算法等知识点&#xff0c;全系列6万字。 一、基础篇 P1 Java虚拟机导学课程 P2 初识JVM 什么是JVM Java Virtual Machine 是Java虚拟机。…

Flutter中Future和Stream关系

Future和Stream类是Dart异步编程的核心。 Future 表示一个不会立即完成的计算过程。与普通函数直接返回结果不同的是异步函数返回一个将会包含结果的 Future。该 Future 会在结果准备好时通知调用者。 Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable&#xff0c;…

(三)softmax分类--九五小庞

softmax分类 对数几率回归解决的是二分类的问题&#xff0c;对于多个选项的问题&#xff0c;我们可以使用softmax函数&#xff0c;它是对数几率回归在N个可能不同的值上的推广 softmax各样本分量之和为1&#xff0c;当只有两个类别时&#xff0c;与对数几率回归完全相同 损失…

多个版本的Python如何不冲突?

转载文章&#xff0c;防止忘记或删除 转载于&#xff1a;电脑中存在多个版本的Python如何不冲突&#xff1f; - 知乎 (zhihu.com) 如何安装多版本的Python并与之共存&#xff1f; 如果你的工作涉及到Python多版本之间开发或测试&#xff0c;那么请收藏本文&#xff0c; 如果你…

Jvm之内存泄漏

1 内存溢出 1.1 概念 java.lang.OutOfMemoryError&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现OutOfMemoryError。产生该错误的原因主要包括&#xff1a;JVM内存过小。程序不严密&#xff0c;产生了过多的垃圾。 程序体现: 内…

CSS 【详解】响应式布局(含 rem 详解)

响应式布局&#xff1a; 同一页面在不同的屏幕上有不同的布局&#xff0c;即一套代码自适应不同的屏幕。 为什么 rem 能用于实现响应式布局&#xff1f; px 绝对长度单位&#xff0c;不同客户端表现都相同&#xff0c;不具有响应式em 相对长度单位&#xff0c;相对于父元素的 f…

【MATLAB源码-第147期】基于matlab的QPSK调制解调在AWGN信道,瑞利信道,莱斯信道理论与实际误码率对比仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 四相位移键控&#xff08;QPSK&#xff0c;Quadrature Phase Shift Keying&#xff09;是一种重要的数字调制技术&#xff0c;它通过改变信号的相位来传输数据。与其他调制技术相比&#xff0c;QPSK在相同的带宽条件下能够传…

【HTML】HTML基础4.2(锚点链接)

目录 解释锚点链接 “公式” 例子 点击回首页 解释锚点链接 在我们浏览网页的时候&#xff0c;总有目录一样的功能&#xff0c;比如 这个时候&#xff0c;只要点击相应目录&#xff0c;就可以直接跳转到相应界面&#xff0c;比如&#xff0c;点击“演职员表” 今天就让我们一…

Leetcode438. 找到字符串中所有字母异位词 -hot100

题目&#xff1a; 代码(首刷看解析 2024年3月2日&#xff09;&#xff1a; 感觉自己这个ac率根本不可能找得到实习 class Solution { public:vector<int> findAnagrams(string s, string p) {int plen p.size(), slen s.size();if (slen < plen) return {};vector…

蓝桥杯备战刷题four(自用)

1.砝码称重 #include <iostream> #include <vector> using namespace std; const int N110; const int M100010; int w[N]; int n; int f[N][M]; int m; int ans; //f[i][j]表示到第i个砝码进行放置时的称得的重量为j的方案数 int main() {cin>>n;for(int i1…