文章目录
- 前言
- 一、面向过程和面向对象初步认识.
- 二、类的定义
- 二、类的作用域
- 三、类的实例化
- 四、类的访问限定符及封装
-
- [1 .访问限定符](#1 .访问限定符)
- 2.封装
- 五、this指针
- 六、C语言和C++实现栈的对比
- 七、类的大小计算
- 总结
前言
今天呢小编想与大家分享C++关于类和对象的相关知识点。由于类和对象这个板块呢知识点比较多。所以 小编分为了上中下三篇来给大家分享。接下来就跟着小编进入类和对象的章节吧。
一、面向过程和面向对象初步认识.
这里我们结合C语言来与C++ 作比较:
C语言是面向过程的。关注的是过程。分析出求解问题的步骤。然后在通过函数的调用来逐步解决问题
C++是基于面向对象的。关注的是对象,将一件事拆成不同的对象,靠对象 之间的交互完成问题。
二、类的定义
c
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分
号不能省略
类体中内容称为类的成员 :类中的变量称为类的属性或成员变量 ; 类中的函数称为类的方法或者成员函数。
类的两种定义方式:
- 声明和定义全部放在类体中 ,需注意:成员函数如果在类中定义,编译器可能会将其当成内
联函数处理。
c
class Book//书
{
public:
void Print()//显示书的相关特性
{
cout << _name << _price << _number << endl;
}
private:
char _name;//书名
int _price;//价格
char _number;//编号
};
- 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
c
test.h文件
using namespace std;
class Book//书
{
public:
void Print();//显示书的相关特性
private:
char _name;//书名
int _price;//价格
char _number;//编号
};
test.cpp文件
#include"test.h"
void Book::Print()
{
cout << _name << _price << _number << endl;
}
二、类的作用域
类定义了一个新的作用域 ,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
c
class Data//日期
{
public:
void Dataday(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void DataPrint()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
class Data//日期
{
public:
void Dataday(int year, int month, int day);
void DataPrint();
private:
int _year;//年
int _month;//月
int _day;//日
};
void Data::Dataday(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Data::DataPrint()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
就像这样,这样其实跟我们之前的学的namespace命名空间一样的。都是重新定义了一个新的域,当我们在外边定义成员或者调用成员函数的时候,就需要用到作用域限定符(::)了,通过作用域限定符来找到要找的 成员 。
三、类的实例化
概念:用类类型创建对象的过程,称为类的实例化
c
#include<iostream>
using namespace std;
class Data//日期
{
public:
void Dataday(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void DataPrint()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Data d1;
d1.Dataday(2024,11,12);
d1.DataPrint();
return 0;
}
用类类型创建对象的过程,称为类的实例化。代码中的d1就是类的实列化,
注意:
- 类是对对象进行描述的 ,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它;
c
class Data
{
public:
private:
int _year;//年
int _month;//月
int _day;//日
};
既然类是堆对象的描述,不开辟新的空间。但是这里为什么要用到int呢?这个不是在定义了吗?既然定义了那就要开辟新的空间了呀?这里我们不能还是跟以前一样理解成定义了。这里它是声明,声明是不占用空间的。所以这里呀注意一下:
- 一个类可以实例化出多个对象 ,实例化出的对象 占用实际的物理空间,存储类成员变量
c
int main()
{
Data d1;
Data d2;
d1.Dataday(2024,11,12);
d1.DataPrint();
d2.Dataday(2024, 11, 12);
d2.DataPrint();
return 0;
}
d1和d2就是类的实例化
如果这里直接要使用_year是会报错的。因为Data是没有空间的,只有Data实例化出来的对象才有空间。
打个比方:类的实例化出来的对象就好像我们在现实生活中使用建筑设计图建房子一样。然而类就是设计图,他只是设计出了要建筑的什么样的房子,但是在现实中是没有实物存在的。而实例化就是根据设计图建筑出来的实物,是具体存在的东西。同样类只是一个设计,实例化出来的对象才能实际储存数据,占用物理空间。
四、类的访问限定符及封装
1 .访问限定符
大家可以发现我们之前的代码里面出现的public和private是什么呢?它的意思就是跟他的英文意思一样的,是公共的和私有的意思,在C++中我们把他们叫做访问限定符 当然还有一个叫做protected(保护)
访问限定符的说明:
1.public修饰的成员在类外可以直接访问。
2.protected和private修饰的成员的在类外是不可以直接访问的(此处protected和private是类似的)。
3.访问权限作用域是从该访问限定符出现的位置到下一个访问限定符出现的位置为止
4.如果后面没有访问限定符,那作用域就到"}"结束。
5.class默认为的是private(私有)的,struct则是public(共有),因为这里struct要兼容C。
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
2.封装
【面试题】面向对象的三大特性:封装、继承、多态。
在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来
和对象进行交互
封装本质上是一种管理,让用户更方便使用类。比如:对于电脑这样一个复杂的设备,提供给用
户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互,完成日
常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件。对于计算机使用者而言,不用关心内部核心部件,比如主板上线路是如何布局的,CPU内部是如何设计的等,用户只需要知道,怎么开机、怎么通过键盘和鼠标与计算机进行交互即可。因此计算机厂商在出厂时,在外部套上壳子,将内部实现细节隐藏起来,仅仅对外提供开关机、鼠标以及键盘插孔等,让用户可以与计算机进行交互即可
五、this指针
c
#include<iostream>
using namespace std;
class Data//日期
{
public:
void DataInit(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void DataPrint()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Data d1;
Data d2;
d1.Dataday(2024,11,12);
d1.DataPrint();
d2.Dataday(2024, 10, 11);
d2.DataPrint();
return 0;
}
对于上述类,有这样的一个问题:
Date类中有 DataInit 与 DataPrint 两个成员函数,函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,该函数是如何知道应该设置d1对象,而不是设置d2对象呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个"非静态的成员函数"增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有"成员变量"的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
this指针:
编译器在编译之后,类的成员函数会默认都会在形参的第一个位置,增加一个当前类类型的指针,叫做this指针,比如在上面的类Data中函数 DataInit和DataPrint的原型就是。
c
class Data//日期
{
public:
void Dataday(Data *const this ,int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void DataPrint(Data* const this)
{
cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
}
//private:
int _year;//年
int _month;//月
int _day;//日
};
但是如果直接怎样写的话程序汇报错的。具体原因是C++规定不能在实参和形参的位置显示的写this指针(编译器会自己处理)但是可以在函数体里面使用。(就像上面的函数体里面的this指针一样)
那在实参的位置不显示写this指针又是怎样的?首先我们要知道想要通过改变形参而改变实参,那就要传一个地址过去,这里我们要传的是类对象的地址,但是我们光从代码的角度去看是没有传地址过去的。
c
int main()
{
Data d1;
Data d2;
d1.Dataday(2024,11,12);
d1.DataPrint(&d1);//会报错的,这里不加&d1
d2.Dataday(2024, 10, 11);
d2.DataPrint(&d2);//会报错的,这里不加&d2
return 0;
}
这里代码会报错的哟。因为这里函数在调用的时候编译器会自动把自己的类对象的地址传过去。
按照逻辑代码要像这样写才对呀!这里逻辑是没有问题的,但是C++规定:不能在实参和形参的位置显示的写this指针(编译器会自己处理)
this指针的特性
1.this指针的类型是:类类型*const,在成员函数中是不能给this赋值的。
2.只能在"成员函数"的内部使用。
3.this本质上是"成员函数"的形参,当对象调用成员函数的时候将对象的地址作为实参传给this形参。所以对象中不存储this指针
4.this指针是"成员函数"第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递
六、C语言和C++实现栈的对比
C语言实现栈之前小编就分享过了,逻辑上时相同的,但是实现代码是比较不同的:https://editor.csdn.net/md/?articleId=142706511
还有什么问题呢大家可以看看。接下来呢我们用C++来实现栈:
cpp
#include<iostream>
typedef int SLDataType;
class Stack
{
public :
void StackInit( SLDataType capacity=4 )//初始化
{
SLDataType*tmp = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
if (tmp == nullptr)
{
perror("malloc fail\n");
exit(1);
}
_arr = tmp;
_capacity = capacity;
_top = 0;
}
void StackPush(const SLDataType& data)//入栈,防止数据被篡改所以这里用到了const修饰
{
if (_capacity == _top)
{
SLDataType newcapacity = _capacity*2;
SLDataType* tmp = (SLDataType*)realloc(_arr, newcapacity * sizeof(SLDataType));
if (tmp == nullptr)
{
perror("realloc fail\n");
exit(1);
}
_arr = tmp;
_capacity = newcapacity;
}
_arr[_top++] = data;
}
SLDataType StackTop()//取出栈顶元素
{
return _arr[_top - 1];
}
void StackPop()//出栈
{
_top--;
}
void StackDestroy()//销毁
{
assert(_arr);
free(_arr);
_arr = nullptr;
_capacity = _top = 0;
}
bool StackEmpty()//判空
{
return _top == 0;
}
private://声明
int* _arr;
int _capacity;
int _top;
};
int main()
{
Stack d1;
d1.StackInit(1);
d1.StackPush(1);
d1.StackPush(2);
d1.StackPush(3);
d1.StackPush(4);
while (!d1.StackEmpty())
{
cout << d1.StackTop();
d1.StackPop();
}
return 0;
}
C++实现栈的优点:
1.C++通过类实现栈具有封装性。能够防止程序的重要变量被随意修改 。
(2)C++通过类实现栈类中默认存在构造函数 可以初始化栈对象,也默认存在析构函数 可以释放和清理申请的空间(后面小编会分享)。和C语言使用栈相比可以不用在担心在创建栈时是否忘记初始化和删除栈了。
C语言实现栈的缺点:
1.每个函数的第一个参数都是结构体指针(ST*)(以小编之前分享的栈的实现为主)
2.函数中必须要对第一个参数检测,也就是断言一下(assert)因为该参数可能会为NULL
3.函数中都是通过ST*操作栈的
4.调用时必须传结构体变量的地址
5.结构体只是定义和存放数据的结构,操作数据的方法不能放在结构体中,所以操作数据的方式和数据分离的。而去实现相对比较复杂,涉及大量指针操作。容错率低。
七、类的大小计算
先来看看规则:
1.类的大小计算要遵循结构体的对齐原则。
https://editor.csdn.net/md?articleId=143825655
2.类的大小与普通数据成员有关,与成员函数和静态成员无关。即普通成员函数,静态成员函数,静态数据成员,静态常量数据成员均对类的大小无影响
c
using namespace std;
class A
{
public:
void Print1()
{
cout << "A::Print()" << endl;
}
static void Print(); //静态成员函数
private:
static const int a =10; //静态常量数据成员,在类外定义时,要加上const关键字
static int b; //静态数据成员
};
int main()
{
A b;
cout << sizeof(b) << endl;
return 0;
}
代码运行的结果是1,说明这里没有计算它的大小。那为什么1就是没有计算呢?那就要看第四点了。
3.虚函数对类的大小有影响,是因为虚函数表指针带来的影响,虚继承对类的大小有影响,是因为虚基表指针带来的影响(这一点呢小编不做分享大家知道就行)
4.空类的大小是一个特殊情况,空类的大小为1
c
class A
{
//空类
};
那为什么没有成员变量还要给1呢?这里就是纯粹的占位标识对象的存在。如果一个字节都不给,那怎样表示对象存在呢?
总结
今天就到这里吧,有什么问题我们评论区间啦!再见啦!