「C++」类和对象1

个人主页:Ice_Sugar_7
所属专栏:C++启航
欢迎点赞收藏加关注哦!

文章目录

  • 前言:初识面向对象
    • 类的定义
    • 访问限定符
      • 类的作用域
    • 类的封装
    • 类的实例化
    • 访问对象的成员
    • 计算对象的大小
      • 补充:内存对齐的意义
  • 成员函数的this指针
  • 写在最后

前言:初识面向对象

C++是一门面向对象的语言,关注解决问题的对象各个对象之间的关系。比如用洗衣机洗衣服,对象就是人、洗衣机、衣服、洗衣液。洗衣服的过程就是这四个对象交互完成的:人把洗衣液倒进洗衣机,然后把衣服放进去。不用关心具体是怎么浸泡、洗多久等问题。通过建立各对象之间的关系,就可以解决问题了

在初阶数据结构中,我们实现栈时使用结构体,并在里面定义数组、栈顶、容量等变量。但是C语言中结构体无法定义函数,而在C++中,结构体升级为类,可以在里面定义函数,不过一般我们用class来定义类

类的定义

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

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

类里面可以定义函数(成员函数,又称为方法),比如你要实现一个栈,在C语言中只能写在全局,而在C++中可以直接写在类里面。这样会带来什么好处呢?你写初始化函数、入栈函数的时候参数部分就不用写栈了。此外,在类里面定义的函数默认是内联函数,不过由于内联函数是否展开取决于编译器的处理,所以一般建议短的函数在类里面定义,长的函数就放个声明,即声明与定义分离。

(类也是一个域,同名函数只有在同一个域里面才会重载)

访问限定符

class当中有三种访问限定符:

public修饰的成员在类外可以直接被访问
protectedprivate修饰的成员在类外不能直接被访问

(在学到继承之前,可以认为protected约等于private)
class中不使用访问限定符的话默认所有成员都是private,而struct为了兼容C语言,默认所有成员都是public

类定义的惯例是:一般情况下,成员变量都是私有的。不过这不是硬性规定,还是要根据实际情况进行调整。

类的作用域

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

访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现或类结束时为止

类的封装

面向对象有三大特性:封装继承多态
在类和对象阶段,主要是研究类的封装特性。所谓封装,就是将数据操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互
其实就是把一个复杂的东西包装起来,只留下简单、方便操作的部分给使用者使用。比如:对于电脑这样一个复杂的设备(对象),提供给用户的就只有开机键、键盘、显示器、USB插孔等(对外公开接口),让用户和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件(对象的属性和实现细节)它们被封装起来。对于计算机使用者而言,不用关心内部核心部件,只需知道怎么开机、怎么通过键盘和鼠标与计算机进行交互即可

类的实例化

类的实例化指的是用类类型创建对象的过程

类是对对象进行描述,就像一个模型,限定了类有哪些成员。定义出一个类并没有分配实际的内存空间来存储它,而类实例化出的对象,占用实际的物理空间,存储类成员变量
关于类和对象的关系,有一个形象的比喻:一个类相当于是一栋房子的设计图,设计图肯定不能直接住人的(即类里面不能存数据),只有用设计图建出来房子才可以住人。而一个设计图可以建出多栋房子,一个类也可以对应多个对象。

对象里面只存成员变量,不存函数,因为所有对象的函数都是一样的,如果一个对象存储1个函数,那么100个对象就需要存储100个一样的函数,很浪费空间。因此,我们直接把函数放到公共的区域(公共代码区
(再举一例,这就好比一个小区,每个住户都有一套房,但是篮球场、泳池这些总不可能一户配一个吧?那就很逆天了)

补充:
区分变量是声明还是定义只需看是否有给它开一块空间,若没有则是声明。声明的变量是不能直接给它赋值的,因为赋值的前提是这个变量有空间

访问对象的成员

访问成员其实和访问结构体成员大差不差了。如果是类,那就通过点运算符.访问;若是类的指针,则用箭头运算符->。这里不多赘述。

计算对象的大小

计算大小涉及到学习结构体时的内存对齐的规则,先来回顾一下:

①第一个成员在与结构体偏移量为0的地址处,其他成员变量要对齐到对齐数的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
(VS中默认的对齐数为8)
结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐数取最小)的整数倍
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

前面说过,成员函数并不在对象里面,所以只需计算成员变量的大小就ok了。

class Date {
public:
	void Print() {
		cout << _year << "." << _month << "." << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

比如这个日期类,显然它的大小就是12个字节:三个int 分别占0 1 2 3,4 5 6 7,8 9 10 11(这里的数字就是相对类起始地址的偏移量)

另外,无成员变量的类,对象大小为一个字节,这个字节不存储有效数据(有点像链表的哨兵位),只是用来标识定义的对象存在过。只有成员函数的类也是这样,因为成员函数并不在对象里面

补充:内存对齐的意义

内存对齐通过规定变量所占用的内存空间的大小,使数据在内存中按照一定的规律排列,尽可能地减少内存控制器读取内存单元的次数(内存控制器一次只能读取一个内存单元,读取次数越多,访问速度就越低),从而提高存储器访问的效率和速度。


成员函数的this指针

前面提到,成员函数是公共的,而每个对象调用同一个函数,如果对象之间成员变量不同的话,一般就会得到不同的结果,也就是说,成员函数可以识别不同对象的成员变量。可是有的函数根本没有参数,那是怎么识别出来呢?
比如说现在有一个日期的类,初始化之后可以打印日期,在主函数中定义了d1d2两个对象,并且都调用Init这个函数,但是函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?

class Date {
private:
	int _year;
	int _month;
	int _day;

public:
	void Init(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}

	void Print() {
		cout << _year << "-" << _month << "-" << _day << endl;
	}

};

int main() {
	Date d1,d2;
	d1.Init(2023, 11, 25);
	d1.Print();
	d2.Init(2024, 11, 25);
	d2.Print();
	return 0;
}

「C++」类和对象1_第1张图片
因为C++编译器在每个非静态成员函数的参数添加了一个this指针并将其隐藏起来(既然this是函数的参数,那么它就存在栈帧上),该指针指向当前对象(哪个对象调用这个函数,this就指向谁),在函数体中所有“成员变量”的操作,都是通过该指针去访问。比如d1调用Init函数,那么this指针就指向d1。实际效果就是这样:

void Init(int year, int month, int day) {
		this->_year = year;
		this->_month = month;
		this->_day = day;
}

int main() {
	Date d1,d2;
	d1.Init(this, 2023, 11, 25);
	d1.Print();
	return 0;
}

(参数部分默认存在指针,所以你不要再人为添加上去,否则会报错)

然后来说一个重要的点,因为我们也会通过指针来调用成员函数,此时this指向的是这个指针所指向的对象,而非指针本身
(举个栗子)

class MyClass {
public:
    void printAddress() {
        cout << "Address of this object is: " << this << endl;
    }
};

int main() {
    MyClass obj1;
    MyClass* obj2 = &obj1;
    obj1.printAddress();
    obj2->printAddress();
    return 0;
}

「C++」类和对象1_第2张图片
可以看到这两个的地址是一样的,其实都是对象obj1的地址

注意:对象的指针访问成员函数(方法)时,箭头运算符(->)表示访问,而非解引用,因为方法的地址并不在指针所指的对象里面
比如下面这段程序,表面上看p是空指针,不能解引用,但实际上是在访问函数,所以可以打印出结果,不会报错。

class A
{
public:
    void Print()
    {
        cout << "Print()" << endl;
    }
private:
    int _a;
};
int main()
{
    A* p = nullptr;
    p->Print();
    return 0;
}

「C++」类和对象1_第3张图片
补充
(p虽然为空,但它也并非毫无作用,因为它指向A类的对象,那么成员函数Print也应该在A类之中,编译链接的时候就会到A这个类里面找一下有没有这个函数,如果没有那就会报编译错误;若有,并且声明和定义分离的话,就会去找函数的地址。换而言之,p可以为编译器指明去A类中找Print函数

下面这个虽然和上面的很像,但是程序崩了:

class A
{ 
public:
    void PrintA() 
   {
        cout<<_a<PrintA();
    return 0;
}

为啥崩了呢?因为打印_a时,其实就是打印this->_a,而this是空指针,对它解引用程序自然就崩咯


写在最后

以上就是本篇文章的全部内容了,如果觉得本文对你有所帮助的话,那不妨点个小小的赞哦!(比心)

你可能感兴趣的:(C++启航,c++,开发语言,算法)