目录
前言
1. 面向对象和面向过程
2. 类的引入
3. 类的定义
3.1 类的定义方法
4. 类的访问限定符和封装
4.1 访问限定符
4.2 封装
5. 类的作用域
6. 类的实例化
7. 类对象模型
7.1 类对象的大小
8. this指针
前言
在初学C语言时,想必大家都听说过 “ 面向过程 ” 和 “ 面向对象 ”,C语言是面向过程的编程语言,而我们常用的C++,Java、python都是面向对象的编程语言,那什么面向过程,什么又是面向对象?它们有什么区别呢?那么今天我们就来了解一下C++中的类和对象。
1. 面向对象和面向过程
C语言是面向过程的编程语言,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决。有人以洗衣服为例,进行了形象的比喻,把C语言实现一个功能比作洗衣服,那么它的过程如下边的流程一样:
C++是基于面向对象的编程语言,它关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
可分为四个对象:人、衣服、洗衣机、洗衣液
洗衣服整个过程是由四个对象交互完成。
2. 类的引入
在C语言中:
结构体中只能定义变量
而在C++中:
结构体内不仅可以定义变量,也可以定义函数
比如:
struct Data
{
void Init(int year,int month,int day)//成员函数
{
_year = year;
_month = month;
_day = day;
}
void Print()//成员函数
{
cout << _year << _month << _day << endl;
}
int _year;
int _month;
int _day;
};
C语言中的结构体也可以被称为类
可以说是一个极简的类
C++中兼容C语言struct所有用法
C++ 中将struct升级成了类
C++更喜欢用class来代替
3. 类的定义
class MyClass
{
// 类的成员和函数声明
}; //注意分号不能省略
- class为定义类的关键字
- MyClass为类的名字
- { }中为类的主体
- 类体中内容称为类的成员
类中的变量称为类的属性(成员变量)
类中的函数称为类的方法(成员函数 )
3.1 类的定义方法
- 声明定义都放在类里
成员函数在类中定义:
class Data
{
public:
void Print()
{
cout << _year << _month << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
成员函数定义在类中,会被当作内联函数处理
注意:
- 成员函数默认直接在类中定义就是inline
- 长的函数最好声明和定义分离
- 短小的函数可以直接在类里定义
- 声明与定义分离
//Date.h
class Data
{
public:
void Print();
private:
int _year;
int _month;
int _day;
};
//Date.cpp
void Date::Print()
{
cout << _year << _month << _day << endl;
}
命名建议:
大家可能有疑惑,为什么我要在变量前加 “_”
比如:
void Init(int year,int month,int day)
{
year = year;
month = month;
day = day;
}
在写一些初始化函数时,将值赋给类中的变量
这样写容易分不清楚哪个是成员变量
为了便于区分,建议这样写:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
4. 类的访问限定符和封装
4.1 访问限定符
封装时不可能将所有的成员都让外部用户使用
访问限定:
选择性的将其接口提供给外部的用户使用
限定符说明:
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束
- class的默认访问权限为private,struct为public(因为struct要兼容C)
C++中struct和class的区别是什么?
- C++中struct可以当成结构体使用,还可以用来定义类
- struct定义的类默认访问权限是public,class定义的类默认访问权限是private
4.2 封装
为了便于给外部用户使用于是就引入了封装的概念
- 数据和操作数据的方法有机的结合
- 隐藏对象的属性和实现细节
- 仅对外公开接口来和对象进行交互
封装本质上是一种管理,让用户更方便使用类
5. 类的作用域
类的作用域体现在成员函数的声明和定义分离
在类的外边定义成员函数时需要指定域
//Date.h
class Data
{
public:
void Print();
private:
int _year;
int _month;
int _day;
};
//Date.cpp
void Date::Print()//Date::域作用限定,限定成员函数Print()属于类Date
{
cout << _year << _month << _day << endl;
}
6. 类的实例化
类的实例化,就是创建对象的过程
类是对对象的描述,并没有分配实际的空间
int main()
{
Date.year=2023;//报错
return 0;
}
Date类没有实际存储空间
类就像是设计图,可以通过图纸建造多个房子
- 一个类可以实例化(创建)出多个对象
- 实例化出的对象 占用实际的物理空间,存储类成员变量
7. 类对象模型
7.1 类对象的大小
class Data
{
public:
void Print()
{
cout << _year << _month << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
类中既可以有成员变量,也可以有成员函数,要如何计算?
上述类的大小为12
在类的实例化中提到过:
实例化出的对象 占用实际的物理空间,存储类成员变量
- 成员变量存在对象中
- 成员函数的地址不在对象中
总结:
类大小计算和结构体大小计算相同(不考虑函数)
结构体大小计算详细可见:从头开始:自定义类型入门指南(结构体、位段、枚举、联合)
- 为了更有效的使用空间,成员函数使用的是一个独立的空间(公共代码区)
- 不同对象调用相同成员函数,实际上调用的是同一个函数
那空类或者说只有成员函数的类大小是多少
class A//大小1字节
{};
class B//大小1字节
{
public:
void f2() {}//成员函数在独立的空间
};
为什么是占一个字节?
- 无成员变量的类,对象大小开一个字节,这个字节不存储有效数据
- 标识定义的对象存在过
8. this指针
不同的对象调用同一函数,函数如何区分?
class Data
{
public:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << _month << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023,10,24);
Date d2(2022,10,24);
d1.Print();//调用相同的print函数(没用参数)输出结果不同
d2.Print();
return 0;
}
为了便于区分,于是引入了this指针的概念。
- C++中非静态的函数成员都有个隐藏的this指针
- 指向当前对象(函数运行时调用该函数的对象)
- 函数体中所有成员变量,都是通过该指针去访问
- 用户不需要来传递,编译器自动完成
- this指针为*const类型(成员函数中,不能给this指针赋值)
所以真实调用是这样的:
void Print(Date* this)
{
cout << this->_year << this->_month << this->_day << endl;
}
虽然this指针在参数列表里没有显示,但是可以在类里显示使用
面试题:
this指针存在哪里?
this指针存在栈帧里(VS中,存到了寄存器ecx中)
this指针可以为空吗?
我们以下面的代码为例:
代码一:
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();//这里并不是传p的地址过去,传的是p
return 0;
}
代码一可以正常运行
为什么?
前边提到,成员函数不在对象里边,所以找Print函数时并不需要通过A的指针来访问,并且Print函数内部也没有访问类的成员变量(没用使用this指针)。这里p的作用仅仅是说明Print函数是A里的成员函数,所以并不会访问到空地址。
代码二:
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
代码二在运行时会运行崩溃
代码一中也做了解释:
成员函数不在对象里边,找PrintA函数时并不需要通过A的指针来访问 ,但是在执行PrintA函数时使用了this指针访问成员变量,这里的this指针是空指针(无效地址),在访问时就会访问冲突,所以会造成运行崩溃(运行错误)。
由此我们可以总结出:
this指针可以为空,但要看它是否访问成员变量,如果通过this指针访问了成员变量就会运行崩溃,如果没有访问那也可以正常运行。
好了以上便是本期全部内容,希望对你有所帮助,最后感谢阅读!