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

类与对象

在C++中,类和对象的出现,是为了完善C语言的不足,在struct的基础上慢慢进步,慢慢完善,将其的功能发挥到最大,也方便使用!

类与对象可谓是非常的重要了,所以在这里我们分为几篇来学习类与对象,在接下来的学习中,我们需要反复琢磨,去好好复习,才能学的好,学的牢,学的扎实!


目录

类与对象

一、面向过程和面向对象初步认识

二、类的引入和定义

三、类的使用,成员的声明和定义

1.类的使用

2.成员的声明与定义的使用

四、类的访问限定符及封装

1.访问限定符

2.封装

五、类的作用域和命名空间区别

六、类对象模型

1.如何计算类对象的大小

2.类对象的存储方式猜测

七、this指针

 1.this指针的引出

 2.this指针的特性

总结



一、面向过程和面向对象初步认识

C语言:面向过程注重的是具体操作,是细节;

C++:面向对象注重的是 大概的步骤。

C 语言是 面向过程 的, 关注 的是 过程 ,分析出求解问题的步骤,通过函数调用逐步解决问题。
C++ 基于面向对象 的, 关注 的是 对象 ,将一件事情拆分成不同的对象,靠对象之间的交互完
成。

比如洗衣服这件事:

面向过程   可能就会对洗衣服的水温,洗涤剂,怎么洗,洗衣机的力度,洗衣服的时间等等这些很小的细节研究

但是面向对象  可能就只会将洗衣服分为三个步骤,浸泡,柔洗,晾干,三个大步骤讲。

那么浸泡,柔洗,晾干,都各是一种大的类型。

浸泡可以选不同的水温,水质等;

柔洗可以选择不同的洗涤剂,揉搓力度等;

晾干可以选择不同的方式,自然风干,加热速干等;

这些都是每一类中的属性,那么单有属性是不可以的,我们还需要行动即方法。

在浸泡这个类中,水温水质都是属性,但我们需要有加水,调温度,这些行动。这些才构成了类!

二、类的引入和定义

C 语言结构体中只能定义变量,在 C++ 中,结构体内不仅可以定义变量,也可以定义函数。
比如:
C 语言方式实现的栈,结构体中只能定义变量
现在以 C++ 方式实现, 会发现 struct 中也可以定义函数
//C语言中
struct Stack
{
      DataType* _array;
      size_t _capacity;
      size_t _size;
}

  void func()  {;}
  ......
//C++中

struct Stack
{
    //成员函数
   void Init(size_t capacity)
  {
     _array = (DataType*)malloc(sizeof(DataType) * capacity);
      if (nullptr == _array)
    {
     perror("malloc申请空间失败");
      return;
     }
     _capacity = capacity;
      _size = 0;
  }


    void Push(const DataType& data)
  {
    _array[_size] = data;
    ++_size;
  }
   ..........

    //成员变量
    DataType* _array;
      size_t _capacity;
      size_t _size;
}

这就是类和对象的优点,它不仅拥有C语言中struct的所有用法,还在其基础上增加了类和对象。

不仅可以定义变量,还可以定义函数!

类的定义:
class className
{
     // 类体:由  成员函数  和  成员变量 组成
};   // 一定要注意后面的分号
  
class 定义类的 关键字(相当于把struct换成了class), ClassName 为类的名字, {} 中为类的主体,注意 类定义结束时后面 号不能省略
类体中内容称为 类的成员: 类中的 变量 称为 类的属性 成员变量 ; 类中的 函数 称为 类的方法 或者 成员函数
即  类=成员函数+成员变量,成员函数就是方法,成员变量就是属性!

三、类的使用,成员的声明和定义

1.类的使用

class Stack // 类型
{

	void Push(int x)
	{
		// Init();
		//...
	}

	void Init(int N = 4)
	{
		// ...
		top = 0;
		capacity = 0;
	}


	int* a;  
	int top;
	int capacity;
};

上面代码我们定义了一个类,但我们可以这样使用吗?

Stack.a=NULL;

Stack.top=0;

当然不行!!

若我们使用struct这样使用的话,就会发现:

struct Stack // 类型
{

	int* a;  
	int top;         //声明
	int capacity;
};

Stack.a=NULL;

Stack.top=0;     这样使用当然不行,struct只是一张图纸,只是声明,声明不会开辟内存空间,只有定义该变量以后Stack s;(定义),才可以使用。

C语言中,Stack s,s叫做变量,但在C++中,s就是对象。

所以回顾以后,我们应该先定义一个对象,通过对象去访问里面的值。

Stack s;

s.a=NULL;

s.top=0; 

2.成员的声明与定义的使用

1. 声明和定义全部放在类体中,需注意:成员函数如果 在类中定义 ,编译器可能会将其当成
联函数 处理。

struct Stack
{
    //成员函数
   void Init(size_t capacity)
  {
     _array = (DataType*)malloc(sizeof(DataType) * capacity);
      ...
     _capacity = capacity;
      _size = 0;
  }
     ......
    //成员变量
    DataType* _array;
      size_t _capacity;
      size_t _size;
}
都放在类中,成员函数中的成员变量无需重新定义,可以直接使用。
2. 类声明放在 .h 文件中,成员函数定义放在 .cpp 文件中,注意: 成员函数名前需要加类名 ::
在.h文件中:  声明
struct Stack
{
    //成员函数
   void Init(size_t capacity);
     ......
    //成员变量
    DataType* _array;
      size_t _capacity;
      size_t _size;
}
在.cpp文件中:  定义    成员函数名前需要加类名 ::
   void  Stack:: Init(size_t capacity)
  {
     _array = (DataType*)malloc(sizeof(DataType) * capacity);
      ...
     _capacity = capacity;
      _size = 0;
  }

就比如数据结构中,很多都需要初始化,但在定义的时候,可能都定义为 Init(),都需要加一个类作用域。  针对声明和定义分开写。


四、类的访问限定符及封装

1.访问限定符

当然,在类中呢,C++还提出了一些其他的东西,public和private:

class Stack // 类型
{
public:
	void Push(int x)
	{
		// Init();
		//...
	}

	void Init(int N = 4) //缺省参数
	{
		// ...
		top = 0;
		capacity = 0;
	}

private:
	int* a;  
	int top;
	int capacity;
};
C++ 实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用
简单地说就是,private是私有,他只能在自己的类中去使用,而public在内外都可以使用,没有限制,这就更好的去使对象完善,方便内外去管理权限并使用。
【访问限定符说明】
1. public 修饰的成员在类外可以直接被访问
2. protected private 修饰的成员在类外不能直接被访问 ( 此处 protected private 是类似的)
3. 访问权限 作用域从该  访问限定符  出现的位置开始直到下一个访问限定符出现时为止

class Stack // 类型
{
public:
    void Push(int x)
    {
        // Init();
        //...
    }

    void Init(int N = 4)
    {
        // ...
        top = 0;
        capacity = 0;
    }
//private:
    int* a;
    int top;
    int capacity;
};

若有private,则public的作用域到private,private的作用域到结尾。

若没有,则直接到结尾,整个都为公开public。

5. class 的默认访问权限为 private struct public( 因为 struct 要兼容 C)
    这就是为什么,struct中,变量访问结构体中的值,可以随便访问。

2.封装

面向对象的三大特性:封装、继承、多态。(最重要的三个特性)
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互。
封装本质上是一种管理,让用户更方便使用类。 一种管理方式。
举例说明:

在C语言中,实现栈时,大家如果都很遵循规则,访问栈顶元素时,就会调用Top函数,但是总会有人直接去 打印st.a[top],但是可能此时的top不一定是栈顶,可能也是栈顶的前一个,每个人实现的方式都不同,所以就会出现问题。

但是在C++中,就体现了封装的作用,是一种管理,就会将类的成员变量设置为私有private,就不会让你去访问,只是通过函数接口来访问,这样就不会出现问题。 

 


五、类的作用域和命名空间区别

类定义了一个新的作用域 ,类的所有成员都在类的作用域中 在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age;
}

那就会有人想,可不可以这样使用  Person::_age = 1;不是命名空间域访问里面的变量时,就可以通过域操作符来访问吗?

但是这里是有区别的,命名空间域相当于设置了一堵围墙,他将里面的变量围了起来,只能通过域操作符来访问到里面变量,函数等,但是类的作用域(类域)它是一堵虚拟墙,他没有实体,就相当于加了密码锁的一张图纸,没有实质内容的,必须按照图纸造出对象以后,才可以通过域操作符来访问。

类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图 ,只设
计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象
才能实际存储数据,占用物理空间

 


六、类对象模型

1.如何计算类对象的大小

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

在计算类对象的大小时,我们可以类比计算 结构体大小,只不过不同的一点是,类中加了成员函数,我们不知道成员函数是否需要占空间???

调用函数时,是通过其地址去找到函数的,那么是函数指针吗??

2.类对象的存储方式猜测

那成员函数到底怎么存储的呢??

缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一
个类创建多个对象时, 每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么
如何解决呢?
代码只保存一份,在对象中保存存放代码的地址

 

只保存成员变量,成员函数存放在公共的代码段,只算成员变量,成员函数不算空间
那么方案三不同于方案二的是,方案三没有将函数地址放到类中。
因为公共区,大家都知道的地方,就没必要每个人再给一把钥匙,直接开放,大家想去就去了。

  那我们就去通过结果去推测:

我们会发现:一个类的大小,实际就是该类中成员变量之和,当然要注意内存对齐 。

注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。

当开辟了多个类时,类里面为空,若不占空间的话,那就是没有,这个类不存在,所以占一个字节要证明这个类是存在的。

所以,现在懂了吗?类的大小只跟成员变量有关系,和成员函数没有关系!
 
忘记内存对齐了吗?
结构体内存对齐规则
1. 第一个成员在与结构体偏移量为 0 的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS 中默认的对齐数为 8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

七、this指针

 1.this指针的引出

我们定义一个日期类:

class Data
{
public:
    void Init(int year, int month, int data)
    {
        _year = year;
        _month = month;
        _data = data;
    }

    void Print()
    {
        cout << _year << " " << _month << " " << _data << endl;
    }
private:
    int _year;
    int _month;
    int _data;
};

int main()
{
    Data d1;
    Data d2;
    d1.Init(2022, 10, 8);
    d2.Init(2022, 10, 9);
    d1.Print();
    d2.Print();
}

想一想,既然成员函数都在公共区中,那么调用的就是同一个函数Print,那么为什么结果不相同呢??

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



void Print(Data*this)
    {
        cout << this->_year << " " << this->_month << " " << this->_data << endl;
    }

原因在这里:当调用类的成员函数时,会在公共区去调用这个函数Print,其默认的第一个参数是this指针,存放调用它的那个类的地址。

 

C++ 中通过引入 this 指针解决该问题,即: C++ 编译器给每个 非静态的成员函数 增加了一个隐藏 的指针参数,让该指针指向当前对象 ( 函数运行时调用该函数的对象 ) ,在函数体中所有 成员变量 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成
  this指针的定义和传递,都是编译器的活,我们不能去抢,但是我们可以再类里面用this指针
所以,这些步骤都是编译器完成的,我们不需要写出来。

2.this指针的特性

1. this 指针的类型:类类型 * const ,即成员函数中,不能给 this 指针赋值。
void Print(Data* const this)
    {
        cout << this->_year << " " << this->_month << " " << this->_data << endl;
    } 
  const在*后面,限制的是指针变量,this指针不可以被修改。
 
Data const*this
const Data*this  这两都是限制的this指针指向的那个变量不能被修改
2. 只能在 成员函数 的内部使用,不可以出了函数使用,只能在函数内部使用。
3. this 指针本质上是 成员函数 的形参 ,当对象调用成员函数时,将对象地址作为实参传递给
this 形参。所以 对象中不存储 this 指针
所以作为形参,他也是在栈帧中,在栈区,但在vs中,因为this指针频繁调用,就放到了ecx寄存器自动传递。

来一道题考考你!

 1.p本身就作为类的地址,直接传递给void Print(),p为空指针,但没有解引用,可以!

2.p直接传递给void Print(),可以,但是 做了一个这样的操作,this->_a,this本身是一个空指针,去访问,那就是解引用了,空指针怎么可能解引用呢??所以是运行错误!


总结

类和对象很好的解决了C语言中的许多问题,其中有很多细节需要我们留心!

下期再见!及时消化!

评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

The s.k.y.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值