【C++】类与对象理解和学习(上)

简介: 【C++】类与对象理解和学习(上)

专栏放在【C++知识总结】,会持续更新,期待支持🌹


1.jpeg


类是什么?



类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它(实例化后才会开辟物理空间,后面会讲到);比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。也可以理解为这是一个自定义类型,与C语言中的结构体(struct)颇有类似。


struct与class



在C语言中,我们知道struct是用来定义结构体的关键字,其实在C++中的struct也依然保留了在C语言中的功能,不仅如此,C++中的struct还可以用来定义函数,并且C语言中,我们用struct来定义一个结构体,那么定义一个与它同类型的变量要写成struct xxx的形式,而C++中用struct来定义一个结构体,那么同类型的变量则可以省略struct。


2.png


类的定义



在C++中,对于这种自定义类型的定义,其实更喜欢用class来代替struct,class为定义类的关键字,class后面为类的名字,{}内为类的主体,主体中的内容就是类的内容,分为成员变量(类的属性)以及成员函数(类的方法)。


3.png


类的两种定义的方式:


一种是将成员函数定义在类里面(编译器可能会当成内联函数处理)


另一种是将成员函数声明与定义分离(工作中推荐第二种)


4.png


这里需要注意的是,在类中定义成员函数以及成员变量时,不需要考虑定义的先后顺序,也就是说,即使成员变量放在成员函数的下面,成员函数中依然可以使用成员变量。


类访问限定符



class中有三种访问限定符:public、protected、private。


5.png


访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到 } 即类结束。


6.png


另外注意:class的默认访问权限为private,struct为public(因为struct要兼容C),访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别


类的实例化以及调用



所谓类的实例化其实就是用类创建一个具体的实体变量,我们上面的class Person中的Person仅仅只是声明,不占用物理空间,只有实例化后,才会占有空间。当然,一个类可以进行多个实例化。


Person p;//p占有实际的物理空间,这里就是一个实例化的过程

//一个类可以进行多个实例化

Person p1;

Person p2;

我们可以通过.操作符来实现类中方法的调用(public影响的区域可以在类外直接调用)


class Person
{
public://公有,在类外可以直接访问
    void Print()
    {
        //...
    }
private://私有,在类外不能直接访问
    char* _name;
    int _age;
    short _height;
};
int main()
{
    Person p;//实例化
    p.Print();//通过.来调用函数Print
    return 0;
}


类对象大小的计算



既然实例化后才会占有具体的空间,那么实例化后,它的空间大小应该是多少呢?成员函数与成员变量又是何存储的呢?假如是个空类,实例化后还会不会占用空间呢?


类对象的存储方式


实际上,成员函数虽然是定义在类中,但是它并不存储在类里,假如它是存储在类中,而每个实例化后的对象都各自拥有各自的成员函数,则会造成严重的资源浪费,因为成员函数就好比小区中的健身器材、公共厕所等公共共有的设施,只需要存在一份就足够了。成员函数实际上是存在于公共代码段中。


7.png


所以实例化后的对象的大小,只需要计算成员变量大小即可,当然,类对象大小的计算与struct一样遵循结构体内存对齐规则。这里需要注意的是,假如一个类中只有成员函数而没有成员变量,或者什么都没有,则实例化后对象的大小为1byte。


class Person
{
public://成员函数不参与大小的计算
    void Print()
    {
        //...
    }
private://遵循结构体内存对齐规则
    char* _name;
    int _age;
    short _height;
};
//空类
class test1
{};
//只有成员函数
class test2
{
public:
    void test()
    {}
};
int main()
{
    Person p;//实例化
    test1 p1;
    test2 p2;
    p.Print();//通过.来调用函数Print
    cout << sizeof(p) << endl;//12
    cout << sizeof(p1) << endl;//1
    cout << sizeof(p2) << endl;//1
    return 0;
}


结构体内存对齐规则(与C语言中的struct相同)


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

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

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

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

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。


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;//实例化
    Date d2;
    //函数Init是如何区分是d1还是d2调用的呢?
    d1.Init(2023, 2, 10);
    d2.Init(2022, 2, 10);
}


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


也就是说,虽然我们这里只是传了三个参数,但实际上还有一个隐藏的参数this指针,也就是说,编译器实际上是这么来处理的:


8.png


this指针的特性


this指针的类型为 Date* const,也就是说,this是不可被修改的,this实际上就是实例化对象的地址

this只能在成员函数内部使用

this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以 对象中不存储this指针。

this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。


9.png


关于this指针为空


只要不涉及到解引用操作,程序都会正常运行,空指针也是可以调用类中的函数的,仅仅只凭借.或者->是不能判断是否涉及空指针的解引用,而是应该根据调用的函数的内容,来判断是否对空指针进行解引用。


class Test
{
public:
    void Print1()
    {
        cout <<"Print()" << endl;
    }
    void Print2()
    {
        cout << _a << endl;
    }
    void Init(int a=20)//缺省参数
    {
        _a = a;
    }
private:
    int _a;
};
int main()
{
    Test* d1 = nullptr;
    //不能仅凭借*以及->来断定就是空指针解引用
    //d1->Print1();//程序正常运行
    //d1->Print2();//程序崩溃,因为函数内容中涉及到了空指针解引用:this->_a(nullptr->_a)
    //d1->Init();//崩溃,原因同上,nullptr->_a=a;
    //(*d1).Print1();//正常运行,函数中仅仅只是打印一个字符串
    //(*d1).Print2();//崩溃,原因同上2
    //(*d1).Init();//崩溃,同上
}


封装的概念与作用



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


就好比我们的使用计算机的时候,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此 计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可。

在C++中便可以通过类来实现数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。这样用户使用起来也会更加方便。


相关文章
|
29天前
|
算法 C语言 C++
C++语言学习指南:从新手到高手,一文带你领略系统编程的巅峰技艺!
【8月更文挑战第22天】C++由Bjarne Stroustrup于1985年创立,凭借卓越性能与灵活性,在系统编程、游戏开发等领域占据重要地位。它继承了C语言的高效性,并引入面向对象编程,使代码更模块化易管理。C++支持基本语法如变量声明与控制结构;通过`iostream`库实现输入输出;利用类与对象实现面向对象编程;提供模板增强代码复用性;具备异常处理机制确保程序健壮性;C++11引入现代化特性简化编程;标准模板库(STL)支持高效编程;多线程支持利用多核优势。虽然学习曲线陡峭,但掌握后可开启高性能编程大门。随着新标准如C++20的发展,C++持续演进,提供更多开发可能性。
45 0
|
2天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
42 30
|
16天前
|
C++
C++(十一)对象数组
本文介绍了C++中对象数组的使用方法及其注意事项。通过示例展示了如何定义和初始化对象数组,并解释了栈对象数组与堆对象数组在初始化时的区别。重点强调了构造器设计时应考虑无参构造器的重要性,以及在需要进一步初始化的情况下采用二段式初始化策略的应用场景。
|
16天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
16天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
16天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
29天前
|
存储 算法 编译器
c++--类(上)
c++--类(上)
|
1月前
|
编译器 C++
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
virtual类的使用方法问题之在C++中获取对象的vptr(虚拟表指针)如何解决
|
16天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
25天前
|
存储 C++
C++ dll 传 string 类 问题
C++ dll 传 string 类 问题
16 0