类与对象
在C++中,类和对象的出现,是为了完善C语言的不足,在struct的基础上慢慢进步,慢慢完善,将其的功能发挥到最大,也方便使用!
类与对象可谓是非常的重要了,所以在这里我们分为几篇来学习类与对象,在接下来的学习中,我们需要反复琢磨,去好好复习,才能学的好,学的牢,学的扎实!
目录
一、面向过程和面向对象初步认识
C语言:面向过程注重的是具体操作,是细节;
C++:面向对象注重的是 大概的步骤。
比如洗衣服这件事:
面向过程 可能就会对洗衣服的水温,洗涤剂,怎么洗,洗衣机的力度,洗衣服的时间等等这些很小的细节研究
但是面向对象 可能就只会将洗衣服分为三个步骤,浸泡,柔洗,晾干,三个大步骤讲。
那么浸泡,柔洗,晾干,都各是一种大的类型。
浸泡可以选不同的水温,水质等;
柔洗可以选择不同的洗涤剂,揉搓力度等;
晾干可以选择不同的方式,自然风干,加热速干等;
这些都是每一类中的属性,那么单有属性是不可以的,我们还需要行动即方法。
在浸泡这个类中,水温水质都是属性,但我们需要有加水,调温度,这些行动。这些才构成了类!
二、类的引入和定义
//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.成员的声明与定义的使用
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;
}
struct Stack
{
//成员函数
void Init(size_t capacity);
......
//成员变量
DataType* _array;
size_t _capacity;
size_t _size;
}
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++ 实现封装的方式: 用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选 择性的将其接口提供给外部的用户使用 。

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。
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.类对象的存储方式猜测
那成员函数到底怎么存储的呢??


那我们就去通过结果去推测:
我们会发现:一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐 。
注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
当开辟了多个类时,类里面为空,若不占空间的话,那就是没有,这个类不存在,所以占一个字节要证明这个类是存在的。
七、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*thisconst Data*this 这两都是限制的this指针指向的那个变量不能被修改
来一道题考考你!
1.p本身就作为类的地址,直接传递给void Print(),p为空指针,但没有解引用,可以!
2.p直接传递给void Print(),可以,但是 做了一个这样的操作,this->_a,this本身是一个空指针,去访问,那就是解引用了,空指针怎么可能解引用呢??所以是运行错误!
总结
类和对象很好的解决了C语言中的许多问题,其中有很多细节需要我们留心!
下期再见!及时消化!