IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】类和对象(第一站)初识类+this指针 -> 正文阅读

[C++知识库]【C++】类和对象(第一站)初识类+this指针

不知你在学习C语言和C++的适合,曾否听过老师这么讲述这两个语言之间的区别:

  • C语言是面向过程的
  • C++是面向对象的

我一直不太理解这二者之间的区别,在查阅了一些博客后,发现了一个比较好的解释👉【传送门】

用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。所谓盖浇饭,北京叫盖饭,东北叫烩饭,广东叫碟头饭,就是在一碗白米饭上面浇上一份盖菜,你喜欢什么菜,你就浇上什么菜。

本篇博客,就让我们从类和对象开始,渐渐了解什么是“面向对象编程”

  • 感谢你关注慕雪,欢迎来我的寒舍坐坐?慕雪的寒舍
  • 我用游戏语音软件创建了一个代码学习社区,欢迎加入!链接

1.类的引入

1.1结构体

在C语言中,我们可以定义自定义类型:结构体。在C++中对结构体的语法进行了扩充,结构体内部不仅能定义变量,还能定义函数

struct Student{
	void Print()
    {
		cout<<_name<<" "<<_sex<<" "<<_age<<" "<<_phone<<endl;
	}
	char _name[20];
    char _sex[8];
    int _age;
    char _phone[20];
};

1.2class

为了和C语言里面的结构体作为区分,我们不再用struct来指代这种包含函数的自定义类型,而是使用class作为它的名字,称之为

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

class为定义类的关键字,ClassName为类的名字,{ }中为类的主体,注意类定义结束时后面分号(这一点和结构体相同)

类中的元素称为类的成员

  • 类中的数据称为类的属性或者成员变量
  • 类中的函数称为类的方法或者成员函数

2.类成员的定义

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

2.1函数声明和定义分离

和普通的函数一样,类同样支持声明和定义分离。下面的代码中,我并没有分离函数的声明和定义

  • 成员函数在类里面定义,编译器会默认处理为内联函数
class Student{
	void Print()
    {
		cout<<_name<<" "<<_sex<<" "<<_age<<" "<<_phone<<endl;
	}
	char _name[20];
    char _sex[8];
    int _age;
    char _phone[20];
};

如果需要在.h中写入类的定义,类外面的.cpp中编写类里面的函数,就需要用到类似于命名空间的使用方法::

class Student{
	void Print();//声明函数
	
	char _name[20];
    char _sex[8];
    int _age;
    char _phone[20];
};

在另外的源文件中定义函数

#include "Student.h"

void Student::Print()
{
	cout<<_name<<" "<<_sex<<" "<<_age<<" "<<_phone<<endl;
}

当我们编写大型项目的时候,一般都会采用声明和定义分离的方式来编写源码,这样可以更方便他人快速查阅我们的头文件,理解代码的“大纲”


这样看起来好像和以前的方式没啥区别。但当我们引入访问权限的概念后,一切都变啦!

2.2访问限定符

class的默认访问权限是私有;struct默认为公有

你不知道什么是访问权限?那就继续往下看👇

我们可以用publicprivate这两个访问限定符来表明某一个具体类型的共有还是私有。它们的最大区别就是在这个类的外部能不能访问

image-20220519091246664

  • public成员可以在类外面直接使用
  • protected和private修饰的成员只能在类里面访问
  • 访问权限的作用域是从该访问限定符出现,到下一个访问限定符出现为止
  • 访问限定符是在编译过程中处理的,并不影响数据在内存上的存放

在前期学习的时候,我们可以认为protectedprivate是相同作用的

class Student{
public:
    //函数在类里面定义,编译器默认为内联函数
    void Print()
    {
        cout<<_name<<" "<<_sex<<" "<<_age<<" "<<_phone<<endl;
    }
    void Init(const char * name,const char*sex, int age,const char* phone)
    {
        strcpy(_name,name);
        strcpy(_sex,sex);
        _age=age;
        strcpy(_phone,phone);
    }
    //函数在类里面声明
    void Delet();

private:
    //这个是对变量的声明
    //变量的声明:没有开辟空间
    //变量的定义:开辟了空间来存放内容
    char _name[20];
    char _sex[8];
    int _age;
    char _phone[20];
    //在创建对象的时候定义
};
  1. 数据和方法分装到一个类里面
  2. 想给你自用访问的(如函数接口)设置为共有
  3. 不想给你访问的(如通讯录每个用户的信息类型)设置为私有

这样可以做到,当函数定义修改之后,只要不修改函数声明,就不需要修改main函数中函数的调用!
这叫做:高内聚,低耦合

2.3封装

为什么会有权限的出现呢?我们可以细想一下下面这个场景:

假设我们编写了一个数组栈的代码,里面有一个top用来标明栈顶的元素位置。一般可以把top定义为0或者1,但是这两种方法的函数使用是不同的!如果有那个铁憨憨跑过来,非要把那个top的定义给改掉,那整个代码就废掉不能用辣!

设立私有和公有的初衷,就是为了避免这种情况。开放一些共有函数供类外面访问,这样对整个代码的访问会变得易于管理。当我们自己修改类里面的代码时,只需要做好处理,就不会影响类外面的函数调用(否则就是直接推翻重写了)

这便是我们常常提到的封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

3.类的实例化

用类类型创建对象的过程,称为类的实例化

  • 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
  • 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

类就好比一个毛坯房,现在毛坯房建好了,要想它变得精致,我们还需要在main中调用这个类,不然毛坯房就要变成烂尾楼了

Student p1;
p1.Init("牛爷爷","男",58,"13251341680");
p1.Print(); 

4.计算类的大小

一般有4种类:包含函数和变量的、只包含函数的、只包含成员变量的、空类

QQ图片20220519093042

让我们来康康如何计算这些类的大小,解析见注释哦!

其实只需要记住,空类和只有函数的类会有1个字节的空间。计算类的空间的时候不会计算函数大小,成员变量的大小计算遵循结构体内存对齐的计算方法就行了!

#include<iostream>
using namespace std;

class A1{
public:
    void func1()
    {
        int ret=3;
        return ;
    }
};
class A2{
};

class A3{
public:
    char _a;
};

image-20220519095032249

5.this指针

5.1特点

当你用同样的图纸建了很多个屋子后,有没有想过应该如何区分它们呢?

C++在设计这部分的时候,添加了一个this指针来解决这个问题:

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

  1. this指针的类型:类名* const
  2. 只能在“成员函数”的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this 形参。所以对象中不存储this指针。
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

5.2显式使用this

就用下面这个函数举例

void Print()
{
	cout<<_name<<endl;
	cout<<_sex <<endl;
}

实际上,在调用它的时候,编译器会做如下处理

void Print(Student*const this)
{
	cout<<this->_name<<endl;
	cout<<this->_sex <<endl;
}

因为只有这样,才能完整的区分两个不同的类


进一步看看下面这个代码,可以帮助你理解this指针

 bool operator==(const Date& d){
        return _year == d._year
            && _month== d._month
            && _day == d._day;
 }

这是一个日期的比较函数,是操作符重载(后面会讲到)

你可以看到,这个函数我们传入了一个Date类型的引用,这是区别于this的另外一个类的对象

如果没有this,那就很难区分两个变量的_year,于是编译器会把它优化成下面这样,就不会存在无法区分的问题了

 bool operator==(Date*const this,const Date& d){
        return this->_year == d._year
            && this->_month== d._month
            && this->_day == d._day;
 }

5.3空指针问题

int main()
{
    int x=10;
    //在程序中,访问NULL不会报错,但是解引用Null会报错
    int*a=NULL;
    int*b=&x;

    //a=x;//err
    *b=20;

    return 0;
}
#include<iostream>
using namespace std;

class ta{
public:
    void Print()
    {
        cout<<"print ta"<<endl;
        //cout<<_a<<endl;
    }

private:
    int _a;
};
int main()
{
    ta* p=nullptr;
    p->Print();
   //可以去访问空指针的函数
   //因为函数只是去调用了类里面的Print函数
   //同时传了一个p的this指针(空指针传参是不会报错的)
   //但是如果你去访问p里面_a变量,就会报运行错误
    return 0;
}

6.类中成员函数名的处理

除了this指针之外,编译器在链接函数名的时候,也做了相应的处理。在Linux里面查看下面这串代码的汇编代码

#include<iostream>

class Test{
public:
    void func1(const int* a1,int* k,const int*a2,char arr)
    {
        *a1+*a2;
        func2(a1,k,a2,arr);
    }
    
private:
    void func2(const int* a1,int* k,const int*a2,char arr)
    {
        *a1+*a2;
    }
};

int main()
{
    Test t;
    int arr1[10];
    int arr2[20];
    int a=10;
    t.func1(arr1,&a,arr2,'a');
    

    return 0;
}

可以找到这两个类的成员函数的地址和函数名

方法参考我的这篇博客👉【末影门】

00000000004006da <_ZN4Test5func1EPKiPiS1_c>:

000000000040071a <_ZN4Test5func2EPKiPiS1_c>:

可以发现,这两个函数的地址不同,但函数名中并没有包含它的公有、私有信息。这也能证明2.2中写道的“访问限定符是在编译过程中处理的,并不影响数据在内存上的存放”


下面是一个普通函数(不在类里面)的函数名

观察类里面的函数名,可以看到比起普通函数,它还包含了类名,来标明自己是被封装在某个类里面的。同时前缀也从_Z变为了_ZH

image-20220519103429289

这里S1_的含义是我多次传相同类型参数,查看汇编代码测试出来的

当我把相同传参的函数放在类外面,重新查看汇编代码

image-20220519103845566

000000000040064d <_Z5func3PKiPiS0_c>:

可以看到它发生了一些变化,比如前缀变为了_Z,函数名后面的E不见了,S1_变成了S0_

虽然我现在还不知道前缀_Z_ZH、函数名后面那个E分别代表什么含义,但是我们可以看的出,这是编译器在编译链接过程中为了区分类中函数和类外函数做的优化


结语

本篇博客是类和对象的第一篇笔记,不知能否帮到你呢

QQ图片20220413084241

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 18:44:48  更:2022-05-21 18:45:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:34:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码