【C++】类和对象(上)

目录

1.面向过程和面向对象的全面认识:

2.类的引入 

3.类的定义

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

4.1封装

4.2访问限定符 

5类的作用域 

6类的实例化 

7.类对象模型

7.1类对象的存储方式

8.this指针 

8.1引言

8.2.this指针出场

8.3.两道题解: 

8.4.this指针的常见问题

8.5声明和定义

声明和定义的区别:


 1.面向过程和面向对象的全面认识:

面向过程——步骤化:是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

面向对象——行为化:面向对象是把整个需求按照特点、功能划分,将这些存在共性的部分封装成对象,创建了对象不是为了完成某一个步骤,而是描述某个事物在解决问题的步骤中的行为

面向过程和面向对象的优缺点:

面向过程:

  • 优点:性能上它是优于面向对象的,因为类在调用的时候需要实例化,开销过大。

  • 缺点:不易维护、复用、扩展

面向对象:

  • 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。

  • 缺点:

    • 低耦合:简单的理解就是说,模块与模块之间尽可能的独立,两者之间的关系尽可能简单,尽量使其独立的完成成一些子功能,这避免了牵一发而动全身的问题。

    • 性能比面向过程低。

2.类的引入 

大家都用过c语言,都使用过结构体,例如用结构体实现顺序表,链表等,但是结构体中只能定义变量。但是C++中的结构体既能定义变量也可应在结构体中定义函数。

但是既然C++是高级语言,大佬们总会想点不一样的,他们就设计出了struct的究极体--类class。

struct Stack
{
    void Init();
    void Push();
    int Top();
    void Destroy();
    
    int* _arr;
    size_t _capacity;
    size_t _size;
};

3.类的定义

class className
{
	//函数声明
	void swap(int m, int n)
	{
		int tmp = m;
		m = n;
		n = tmp;
	}
	// 变量声明
	int a;
	double b;
};
  • class为定义类的关键字ClassName为类的名字{}中为类的主体注意类定义结束时后面分号(一般我们打出大括号他就自动出来了)
  • 类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数

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

4.1封装

谈到封装我们就避不开面向对象的三大过程:封装继承多态

那封装到底是什么?

1、在C++中,当我们使用类的时候,我们首先要注意类的实现细节和类的使用方式(也就是说我们在做任何事情前,先要考虑好事情的大局观甚至加一些要注意的细节问题,不然一拿到一件事情,没有方向性的去做事情,往往最终结果会适得其反!):

  • 当使用类时,不需要关心其实现细节。比如说,对于我们经常使用手机的用户来说,你只需要学习如何发短信,打电话,拍照等,用户并不需要明白这其中的原理。

  • 当创建类时,才需要考虑其内部实现细节。对于我们手机研发工程师来讲的话,就需要明白手机里面的许多工作原理和一些其它细节了,不然就无法开发出手机了。

2、封装的基本概念:

  • 封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
  •      对于类来说,类的每一个属性并不是都对外开放的——就好比来说,女孩子不希望外人知道她的体重和年龄,男孩子不希望外人知道他的身高和实际收入一样。
  •      然而对类来说,它的一些属性是可以对外开放的——想必在学校读书的时候,如果大家注意看学校光荣榜的话,你会发现光荣榜上每一个获得荣誉的人,都会介绍他(她)的姓名和所学专业以及出生地等等信息。

C++面向对象的封装是将数据和行为封装在一起,隐藏实现细节,只暴露必要的接口,提高代码的可维护性和安全性。具体来说,封装包括以下几个方面:

  1. 成员变量私有化:将成员变量声明为private访问权限,防止外部直接访问和修改对象的内部状态,保证数据的安全性。

  2. 成员函数公开化:将需要暴露给外部的接口声明为public访问权限,提供对对象的操作,同时隐藏实现细节,保证接口的简洁性和易用性。

  3. 访问控制:通过public、private、protected关键字控制成员变量和成员函数的访问权限,保证数据的安全性。

  4. 友元函数和友元类:友元函数和友元类可以访问类中的私有成员,增加了灵活性,但也可能降低封装性。

  5. 封装实现细节:通过将实现细节封装在类内部,外部只能通过公开接口访问和修改对象的状态,保证代码的可维护性和安全性。

总之,封装是面向对象编程的重要特征之一,可以提高代码的可维护性和安全性,同时也能够提升代码的可读性和可重用性。

4.2访问限定符 

访问限定符说明:

  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • 如果后面没有访问限定符,作用域就到 } 即 类结束。
  • class的默认访问权限为private,struct为public(因为struct要兼容C)

C++中struct和class的区别是什么?

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

5类的作用域 

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

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

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

上面我叫类中的成员变量是声明一个成员变量,,在这里重复一下,类中的成员变量是声明不是定义,因为它没有被开辟空间。

6类的实例化 

我们用类类型创建对象的过程叫做类的实例化。

类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。

这就像制造大奔一样(希望诸位都能开上大奔)我有制造大奔的图纸(类),这个图纸上写满了被制造的部分,造轮胎,玻璃,发动机等等,但是图纸上的这些东西占地方吗?不占因为它没有被实现,如果我们真的制造出了一辆大奔(实例化出一个对象)图纸上的零件(变量,函数)这辆大奔上都存在,但是这个大奔(包括他的零件)占用了空间。和造房子一样

实例化的好处

类实例化指的是创建类的对象,也就是将类定义转换为具体的实体。类实例化的好处包括以下几个方面:

  1. 封装数据和行为:类实例化可以将数据和行为封装在一起,形成一个独立的对象,对象的内部状态和行为对外部代码是不可见的,保证了数据的安全性和代码的可维护性。

  2. 提供接口:类实例化可以提供接口,让外部代码访问和修改对象的状态,这样一来,对象的使用变得更加灵活和易于扩展。

  3. 提高代码复用性:类实例化可以提高代码的复用性,一个类的定义可以被多次实例化,每个实例都是独立的对象,可以被多个代码模块引用和使用。

  4. 提高代码可读性:类实例化可以提高代码的可读性,一个对象的接口清晰明了,易于理解和使用,代码的结构也更加清晰。

  5. 提高代码可维护性:类实例化可以提高代码的可维护性,当需要修改实现细节时,只需要修改类内部的代码,外部代码不需要做任何改变,提高了代码的可维护性和安全性。

总之,类实例化是面向对象编程的核心特性之一,它可以提高代码的封装性、复用性、可读性和可维护性,是编写高质量、可维护的代码的重要手段。

 7.类对象模型

7.1类对象的存储方式

 当时我们再结构体那张中讲到了结构体的存储方式,类对象的存储方式和结构体的基本一致。

说一下内存对齐规则吧,其他各位有兴趣可以看一下结构体的内存对齐规则。

规则:

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

 8.this指针 

8.1引言

 看一下上面这个代码,对于打印函数而言,我们并没用给main函数的d1, d2进行传参,Print函数也没有参数接收但是编译器为啥能区分开他俩?

8.2.this指针出场

也就是说编译器可以通过一个东西来准确判断每个函数的传参数值,并进行相应运行。

 

  注意:

 总结:一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

C++为啥设计this指针?

  • C++语言设计了 this 指针,是为了解决类成员函数中的命名冲突问题。在类成员函数中,可能会存在与类成员变量同名的局部变量或函数参数,这会导致编译器无法区分到底要访问类成员变量还是局部变量或函数参数。为了解决这个问题,C++语言引入了 this 指针,它指向当前对象的指针。
  • 当对象调用成员函数时,会自动将该对象的地址传入 this 指针,成员函数就可以通过 this 指针访问该对象的成员变量和成员函数。这样,即使成员函数中存在与类成员变量同名的局部变量或函数参数,也可以通过 this 指针来访问类成员变量,从而避免了命名冲突的问题。
  • 另外,this 指针还可以用于在成员函数中返回当前对象的引用,如 return *this;,这在链式调用中比较常见,可以简化代码的书写。
  • 总之,C++设计 this 指针的目的是为了解决类成员函数中的命名冲突问题,同时也可以用于返回当前对象的引用。

8.3.两道题解: 

//(1).下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A 
{
public:
     void Print()
     {
         cout << "Print()" << endl;
     }
private:
     int _a;
};
int main()
{
     A* p = nullptr;
     p->Print();
     return 0;
 }
// (2).下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{ 
public:
     void PrintA() 
     {
         cout<<_a<<endl;
     }
private:
     int _a;
};
int main()
{
     A* p = nullptr;
     p->PrintA();
     return 0; 
}

 答案是C/B

1.为啥第一个能正常运行?虽然P是一个空指针但是在进行p->print是并不会对p进行解引用,而是去公共代码区去寻找Print函数去了,并且这个Print函数体内部没包含成员变量,所以并不会进入到A中,所以正常运行,

使用空指针没错,但是对空指针进行解引用就犯大错了。

2.程序崩溃,因为Print函数中还有一个成员变量_a, 要访问它就要对Print进行解引用,所以解引用空指针会使程序崩溃。

8.4.this指针的常见问题

1:this指针是什么时候创建的?

this在非静态成员中有意义,作为右值可以直接在编译时确定其存在,运行时无所谓创建。

2:this指针存放在何处?堆,栈,全局变量,还是其他?

由上一问可知,this指针无需显式储存内存中。只要存储对象的内存位置确定,对应的this指针就被确定了。一般this指针存在栈区,但是有的编译器进行了结构优化,此时就不会放在栈区

3:this指针如何传递给类中函数的?绑定?还是在函数参数的首参数就是this指针.那么this指针又是如何找到类实例后函数的?

this是通过函数参数的首参数来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。

4:this指针如何访问类中变量的?

如果不是类,而是结构的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,那就很好理解这个问题了。在C++中,struct是一种类类型,struct和class只有一个区别的:class的成员和继承默认的访问控制权限是private,而struct是public。this是class或public的对象的指针。

5:我们只有获得一个对象后,才能通过对象使用this指针,如果我们知道一个对象this指针的位置可以直接使用吗?

this指针只有在非静态成员中才有意义。获得一个对象后,不需要在类外部使用this对其操作。应当注意this是一个右值(方法的一个隐式参数) [2]  ,不存在所谓的this的“位置”,只是this表示了对象的存储位置而已。&this违反语义规则,是错误的用法,不会编译通过。

6:每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?

一般来说,对于类成员函数(不论是静态还是非静态的成员函数)都不需要创建一个在运行时的函数表来保存。只有虚函数才会被放到函数表中。但是,即使是虚函数,如果编译器能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是可以直接调用该函数。 

 

8.5声明和定义

声明和定义的区别:

定义 = 声明  +  初始化(就是给一个值)

①变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。

②变量声明:用于向程序表明变量的类型和名字。

③定义也是声明:当定义变量时我们声明了它的类型和名字。

④extern关键字:通过使用extern关键字声明变量名而不定义它。

 1.定义也是声明,extern声明不是定义,即不分配存储空间。extern告诉编译器变量在其他地方定义了。

 结构体中的next和val、是声明还是定义?

这个是声明,因为系统不会为他俩开辟空间,当我们用ListNode在定义一个结构体变量是才会开辟空间。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值