C++类和对象(上): 封装与this指针

news2024/9/27 17:35:59

目录

一.前言

二. 类的引入和定义

1.C和C++结构体的区别

2.C++类的定义

3.类的成员方法的声明和定义是可分离的

三.面向对象之封装特性

1.封装思想的介绍

2.类封装编程模式的优点

四. 类实例(对象)的内存模型

五.this指针


章节导图: 

 

一.前言

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

1.C语言是面向过程的,关注的是过程分析出求解问题的步骤,通过函数调用逐步解决问题

2.C++是基于面向对象的,关注的是对象将一件事情拆分成不同的对象,靠对象之间的交互完成

对于面向对象的编程思想,我们还需要长时间的学习和积累才可能对其有更深刻的理解。

二. 类的引入和定义

1.C和C++结构体的区别

C语言结构体中只能定义变量在C++中,结构体内不仅可以定义变量,也可以定义函数。(可在其中定义函数的自定义类型为面向对象编程提供了可能)

比如:

代码段1:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <assert.h>
#include <string>

struct STU
{                            
	int _data;                             为了区分结构中成员变量和结构函数的形参,习惯性地在成员变量前标记_
	char _name[20];
	void init(int data, const char* name)  //结构体初始化函数
	{
		assert(name);
		_data = data;                      结构体中的函数可以直接访问结构体的成员变量
		strcpy(_name, name);
	}
	 
	void Prin()                            //结构体打印函数
	{
		cout << _data << endl;
		cout << _name << endl;
	}
};


int main()
{
	STU a;                                  创建一个结构体变量(C++中类型名无需加struct关键字)
	a.init(23, "张三");
	a.Prin();
	return 0;
}

结构体中的函数可以直接访问结构体的成员变量

注意:

为了区分结构(类)中成员变量 和 结构(类)成员函数的形参,习惯性地在成员变量标识名前标记_(具体标记什么无所谓)。                                      

2.C++类的定义

C++的类也是一种类似于C++结构体的自定义类型(前期学习中二者区别不大),不仅可以定义变量,也可以定义函数(类中的函数亦称为方法)

C++类定义:

class className
{                   // 类体:由成员方法和成员变量组成
    public:         // 公有域和私有域可以在类中随意划分
    //公有域的成员
    private:
    //私有域的成员
    public:
    //公有域的成员
    ......
    
};  // 一定要注意后面的分号

相关关键字和公私有域介绍:

(1)class为定义类的关键字,ClassName为类的名字,{}中为类的主体。

(2)类的私有域(由关键字private标识)中的成员只能被类中的方法访问,不能在类作用域(由类的花括号限定)外被访问

(3)类的公有域(由关键字public标识)的成员既可以被类中的方法访问,也可以在类作用域(由类的花括号限定)外被访问

C++类和结构体的一个区别在于成员变量和方法所在默认公私有域不同:

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

(1)结构体的成员变量和方法在用户不指定公私有域的情况下,默认属于public公有域

(2)类的成员变量和方法在用户不指定公私有域的情况下,默认属于private私有域.

(编程中最好不要使用默认的公私属性,要明确地划分好公有域和私有域)

但是在C++中,构建某类对象时我们一般习惯使用class而不是struct(大概是为了和C语言进行区分吧)

3.类的成员方法的声明和定义是可分离的

类的成员方法的声明和定义是可以分离的,我们可以在类的作用域中只写成员方法的声明,然后成员方法的定义可以放在类的作用域外;

比如:

注意:在类的作用域外实现的成员方法标识名前一定要用类名加作用域限定符:: 来标识成员方法属于某个类的作用域。(类似于命名空间,类实质上定义了一个新的作用域)

这种类的实现方式在工程项目中十分有用,可以大大提高代码的可读性和可维护性。

补充:成员方法如果在类的作用域中完成定义,编译器可能会将其当成内联函数处理。内联函数参见:http://t.csdn.cn/r8m2N

三.面向对象之封装特性

1.封装思想的介绍

面向对象的封装思想:将数据和操作数据的方法进行有机结合,通过访问权限的选择限定隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。(类的语法特性就是为封装而设计的)

封装本质上是一种管理,让用户更便捷地使用类。

(打个比方:计算机出厂时,在外部套上壳子,将内部的电路,芯片等等细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等操作接口,让用户可以边界地与计算机进行交互)

2.类封装编程模式的优点

相比于C语言的编程模式(将函数模块暴露在全局作用域中) ,C++的类封装模式具有很多的优越性.

比如现在我们用类来实现一个栈对象。

using std::cin;
using std::cout;
using std::endl;


typedef int DataType;
class Stack
{
public:
    void Init()
    {
        _array = (DataType*)malloc(sizeof(DataType) * 3);
        if (NULL == _array)
        {
            perror("malloc申请空间失败!!!");
            return;
        }
        _capacity = 3;
        _size = 0;
    }
    

    void Push(DataType data)
    {
        CheckCapacity();
        _array[_size] = data;
        _size++;
    }



    void Pop()
    {
        if (Empty())
        return;
        _size--;
    }
    

    DataType Top()
    { 
        return _array[_size - 1];
    }



    int Empty() 
    { 
        return 0 == _size;
    }



    int Size()
    { 
        return _size;
    }

    void Destroy()
    {
        if (_array)
        {
            free(_array);
            _array = NULL;
            _capacity = 0;
            _size = 0;
        }
    }
private:
    void CheckCapacity()
    {
        if (_size == _capacity)
        {
            int newcapacity = _capacity * 2;
            DataType* temp = (DataType*)realloc(_array, newcapacity *
            sizeof(DataType));
            if (temp == NULL)
            {
                perror("realloc申请空间失败!!!");
                return;
            }
            _array = temp;
            _capacity = newcapacity;
        }
    }

private:
    DataType* _array;
    int _capacity;
    int _size;
};


int main()
{
    Stack s;
    s.Init();
    s.Push(1);
    s.Push(2);
    s.Push(3);
    s.Push(4);
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Pop();
    s.Pop();
    printf("%d\n", s.Top());
    printf("%d\n", s.Size());
    s.Destroy();
    return 0;
}

如果用C语言来实现栈:

typedef int DataType;
typedef struct Stack
{
    DataType* array;
    int capacity;
    int size;
}Stack;


void StackInit(Stack* ps)
{
    assert(ps);
    ps->array = (DataType*)malloc(sizeof(DataType) * 3);
    if (NULL == ps->array)
    {
        assert(0);
        return;
    }
    ps->capacity = 3;
    ps->size = 0;
}


void StackDestroy(Stack* ps)
{
    assert(ps);
    if (ps->array)
    {
        free(ps->array);
        ps->array = NULL;
        ps->capacity = 0;
        ps->size = 0;
    }
}


void CheckCapacity(Stack* ps)
{
    if (ps->size == ps->capacity)
    {
        int newcapacity = ps->capacity * 2;
        DataType* temp = (DataType*)realloc(ps->array,newcapacity*sizeof(DataType));
        
        if (temp == NULL)
        {
            perror("realloc申请空间失败!!!");
            return;
        }
        ps->array = temp;
        ps->capacity = newcapacity;
    }
}


void StackPush(Stack* ps, DataType data)
{
    assert(ps);
    CheckCapacity(ps);
    ps->array[ps->size] = data;
    ps->size++;
}

int StackEmpty(Stack* ps)
{
    assert(ps);
    return 0 == ps->size;
}

void StackPop(Stack* ps)
{
    if (StackEmpty(ps))
    return;
    ps->size--;
}


DataType StackTop(Stack* ps)
{
    assert(!StackEmpty(ps));
    return ps->array[ps->size - 1];
}


int StackSize(Stack* ps)
{
    assert(ps);
    return ps->size;
}


int main()
{
    Stack s;
    StackInit(&s);
    StackPush(&s, 1);
    StackPush(&s, 2);
    StackPush(&s, 3);
    StackPush(&s, 4);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackPop(&s);
    StackPop(&s);
    printf("%d\n", StackTop(&s));
    printf("%d\n", StackSize(&s));
    StackDestroy(&s);
    return 0;
}

C++的类封装写法相比于C语言将栈操作函数放置在全局区域而言的好处

(1).C++中通过类可以将数据以及操作数据的方法进行完美结合,将所有栈操作函数封装在类中便于代码的维护和管理

(2).由于类存在公私有访问权限的限定,所以外部用户无法修改栈对象的 _array ,_capacity,_size这三个维护栈的关键信息,而C语言在主函数中是可以随意对维护栈的指针和栈容量等信息进行修改的,因此C++的栈对象使用起来更加安全。

(3).将所有栈操作函数封装在类中,使用时统一通过类成员访问的方式去调用,代码的可读性更高(尤其是当同类型类对象很多的时候)

(4)C语言实现的栈涉及到大量指针操作,稍不注意可能就会出错。

(5)编程思维的转变:从编写功能模块去操作对象的编程思维转变成构造对象去使用自身成员方法的编程思维。

四. 类实例(对象)的内存模型

用定义好的类去创建类对象实例,对象实例在自身的内存区块中只存放了成员变量,类的成员方法(函数体指令段)存放在只读常量区被所有类实例对象所共用

类对象实例的内存区块中,只存有成员变量,而不存放函数体 

有两个例子可以验证这一点:

例一:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <assert.h>
#include <string>

static class STU
{
                               
	int _data;                             //为了区分结构中成员变量和结构函数的形参,习惯性地在成员变量前标记_
	char _name[20];

public:

	void init(int data, const char* name)  //结构体初始化函数
	{
		assert(name);
		_data = data;                      //结构体中的函数可以直接访问结构体的成员变量
		strcpy(_name, name);
	}
	 
	void Prin()                            //结构体打印函数
	{
		cout << _data << endl;
		cout << _name << endl;
	}

	void Prin2()
	{
		cout << "Prin2" << endl;
	}
};


int main()
{
	STU a;                                  //创建一个类对象a
	STU b;                                  //创建一个类对象b
	a.init(23, "张三");
	b.init(24, "李四");
	return 0;
}

创建两个STU对象a,b,分别调用它们方法init,转到汇编代码观察两次调用init时call指令访问到的函数体地址:

这充分说明了多个类对象公用的是同一个函数体代码段。

例二:

代码段(1)

class A
{
public:
    void PrintA()
    {
        cout<<_a<<endl;
    }
private:
    int _a;
};


int main()
{
    A* p = nullptr;        将空指针赋给类指针p
    p->PrintA(); 
    return 0;
}

主函数中没有创建类对象实例,p是空指针(值为0),而PrintA函数中访问了类成员_a,所以调用PrintA方法会导致非法访问内存空间

代码段(2)

class A
{
public:
    void Print()
    {
        cout << "Print()" << endl;
    }
private:
    int _a;
};


int main()
{
    A* p = nullptr;
    p->Print();
    return 0;
}

代码段(2)的不同之处在于Print方法中没有访问类的成员变量,又因为类的方法(函数体)是存放在只读常量区的,并没有存放在任何类对象实例的内存空间中,所以该代码段可以正常运行输出。这也充分说明了类函数体的代码段是独立存放在内存中的。

因此,类对象实例的所占的内存空间根据其成员变量来计算即可,各成员变量的内存分布遵循结构体内存对齐原则.

结构体内存对齐参见:http://t.csdn.cn/eTtYF

五.this指针

1.C++编译器在编译阶段会给的每个非静态(非static修饰)的成员函数(方法)形参表中增加一个隐藏的指针参数(形参).(指针参数名为this,this在C++中被作为关键字)。(编译器添加this指针的操作不会在源码层面改变代码,仅仅只是在编译阶段临时添加而已)

2.类对象调用成员函数时,编译器会自动向成员函数传入当前对象的地址作为this指针的实参使this指针指向当前对象

3.在成员函数体(成员方法)中所有访问成员变量的操作,都是通过this指针实现的

4.this指针的类型为 : (类名)* const; this 指针被const保护无法被修改

比如:

using std::cout;
using std::cin;
using std::endl;
#include <assert.h>
#include <string>

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

编译时:

 ​​

注意:this形参指针在各“成员函数”形参表的第一个位置
我们可以通过汇编来观察一下:

 继续往下调试通过call指令跳转到函数体指令段中

this指针的添加和使用的操作在源码层面是不可见的,都是编译器在编译阶段自动完成的。
但是通过汇编可以很清楚地看到this指针的存在。

this指针的存在使类成员方法可以返回类对象的地址或类对象本身的引用

比如:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;
#include <assert.h>
#include <string>

class Date
{
public:
	Date& Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
		return *this;
	}
	Date* Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};


int main()
{
	Date d1;
	(d1.Init(2022, 1, 11)).Print();
	return 0;
}

 

这种用法在后续的学习中会经常见到,也十分地重要。

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

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

相关文章

分享167个PHP源码,总有一款适合您

PHP源码 分享167个PHP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 167个PHP源码下载链接&#xff1a;https://pan.baidu.com/s/1fzoQ4_4VXc1e1ZHOUKuhbQ?pwdsb6s 提取码&#x…

数学表达式的处理

概述 在OJ上 会遇到一些这样的题目&#xff1a; 小明同学写数学四则运算&#xff0c;有把括号写多、写少、写错的情况&#xff0c;比如&#xff08;AB)*(C-D &#xff0c;请你输入一个表达式&#xff0c;判断此表达式的括号是否正确(不考虑运算的结果正确性)。 每次我看到 &q…

【操作系统】—— Windows压缩工具 “ Bandizip与7-zip ”(带你快速了解)

&#x1f4dc; “作者 久绊A” 专注记录自己所整理的Java、web、sql等&#xff0c;IT技术干货、学习经验、面试资料、刷题记录&#xff0c;以及遇到的问题和解决方案&#xff0c;记录自己成长的点滴。 &#x1f341; 操作系统【带你快速了解】对于电脑来说&#xff0c;如果说…

Python OpenCV 图片滑块验证码 滑块图片验证码 自动识别方案 模板匹配识别 识别成功率调试 源码分析 通用解决方案

前言 通过本专栏前面两篇文章大家已对图片滑块验证码有了初步的了解,对于滑块验证的实现和校验原理有了一定的了解,通过由浅入深的实战案例可直接应用于实战,对于滑块如何在前端实现滑动或接口调用可自行查阅相关资料实现,本文主要讲解 滑块验证码 模板匹配 识别的通用解决…

史上最详细的AVL树的实现(万字+动图讲解旋转)

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️小林爱敲代码       &#x1f6f0;️文章专栏&#xff1a;✈️小林的C之路       &#x1f6f0;️欢迎关注&#xff1a;&#x1f44d…

[Python从零到壹] 六十三.图像识别及经典案例篇之图像漫水填充分割应用

祝大家新年快乐&#xff0c;阖家幸福&#xff0c;健康快乐&#xff01; 欢迎大家来到“Python从零到壹”&#xff0c;在这里我将分享约200篇Python系列文章&#xff0c;带大家一起去学习和玩耍&#xff0c;看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲…

创建者模式-原型模式

1.概述 用一个已经创建的实例作为原型&#xff0c;通过复制该原型对象来创建一个和原型对象相同的新对象 2.结构 原型模式包含如下角色&#xff1a; 抽象原型类&#xff1a;规定了具体原型对象必须实现的的 clone() 方法。具体原型类&#xff1a;实现抽象原型类的 clone() …

Spring AOP与Spring 事务

一、AOP讲解 创建普通对象UserService Component public class UserService{Autowiredprivate OrderService orderService;public void test(){System.out.println(orderService);}}创建代理对象UserServiceProxy&#xff0c;对test&#xff08;&#xff09;方法进行切面编程…

SSM项目 - 博客系统

1.SSM 版本的博客系统相较于 Servlet 版本的升级1. 框架升级 : SSM (SpringBoot Spring MVC MyBatis) MySQL Redis jQuery.2. 密码升级: 明文存储/md5存储 -> 加盐处理.3. 用户登录状态持久化升级: session 持久化到内存 - > session 持久化到 Redis. (后期有空实现…

cmake 03 一个可用的 cmake 工程应当是什么样的

cmake 学习笔记 代码地址: https://gitcode.net/u014254963/cmake-study/-/tree/master/hello_cmake_project https://gitcode.net/u014254963/cmake-study/-/tree/master/hello_cmake_project_vs 本文目标 多目录构建引用自己写的动态库关于单元测试的一些实践使用 python 脚…

Pandas-DataFrame基础知识点总结

1、DataFrame的创建 DataFrame是一种表格型数据结构&#xff0c;它含有一组有序的列&#xff0c;每列可以是不同的值。DataFrame既有行索引&#xff0c;也有列索引&#xff0c;它可以看作是由Series组成的字典&#xff0c;不过这些Series公用一个索引。 DataFrame的创建有多种…

JavaEE-多线程初阶4

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录多线程案例阻塞队列阻塞队列是什么生产者消费者模型标准库中的阻塞队列阻塞队列实现定时器定时器是什么标准库中的定时器实现定…

(第107篇)C规范编辑笔记(十三)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) C规范编辑笔记(五) C规范编辑笔记(六) C规范编辑笔记(七) C规范编辑笔记(八) C规范编辑笔记(九) C规则编辑笔记(十) C规范编辑笔记(十一) C规范编辑笔记(十二) 正文&#xff…

行人属性识别研究综述(二)

文章目录6 PAR&#xff08;行人属性识别&#xff09;算法综述6.1全局基于图像的模型6.1.1 ACN (iccvw-2015)6.1.2 DeepSAR and DeepMAR (ACPR-2015) [6]6.1.3 MTCNN (TMM-2015) [7]6.2 基于部件的模型6.2.1 Poselets (ICCV-2011)6.2.2 rad (iccv-2013)6.2.3 PANDA (cvp -2014) …

Java-IO知识详解(一)

分类分类&#xff08;传输&#xff0c;操作&#xff09;IO理解分类 - 从传输方式上字节流字符流字节流和字符流的区别IO理解分类 - 从数据操作上文件(file)数组([])管道操作基本数据类型缓冲操作打印对象序列化反序列化转换装饰者模式分类&#xff08;传输&#xff0c;操作&…

迈百瑞冲刺创业板上市:关联收入占比较高,房健民为加拿大籍

撰稿|汤汤 来源|贝多财经 近日&#xff0c;烟台迈百瑞国际生物医药股份有限公司&#xff08;下称”迈百瑞“&#xff09;在深圳证券交易所提交更新后的招股书&#xff08;申报稿&#xff09;。据贝多财经了解&#xff0c;迈百瑞于2022年9月在递交IPO申请材料&#xff0c;准备…

指定不同版本的pcl

18.04里面安装了两个版本的pcl&#xff0c;一个是安装ros的时候安装的pcl1.8&#xff0c;另一个是安装的源码pcl1.12版本。一直相安无事&#xff0c;今天在我编译lego-loam的时候&#xff0c;突然就冲突了。卡了我两个小时&#xff0c;到处找原因&#xff0c;网上基本上没有相似…

RSD高分卫星数据处理能力提升——日正射处理数千景高分数据集

李国春 通常认为&#xff0c;能够单日处理几百景高分辨率对地观测卫星数据的系统就已经是非常优秀的卫星数据处理系统了。RSD此次优化将其处理能力提升超过了一个数量级&#xff0c;达到了单日正射处理数千景高分辨率卫星数据集的水平。 不仅如此&#xff0c;RSD达到如此高的…

SpringBoot+Vue项目(学生信息管理系统)搭建运行

项目地址&#xff1a;学生信息管理系统 前端部分&#xff08;Vue&#xff09; 首先以管理员身份运行终端 不然运行命令时有些会报错 1.首先下载node.js 2.打开并安装node.js 3.安装完成&#xff0c;打开控制台&#xff0c;输入node -v查看是否安装完成&#xff0c;如果显示…

MongoDB学习笔记【part2】数据库、文档、集合与常用命令

一、MongoDB 概念 Mongo 与 SQL 的术语区别如下&#xff1a; SQL术语/概念MongoDB术语/概念解释/说明databasedatabase数据库tablecollection数据表 – 集合rowdocument记录 – 文档columnfield字段 – 域indexindex索引table joins表连接&#xff0c;MongoDB不支持primary k…