- C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
- C++是基于面向对象(不是纯面向过程,可以面向对象和过程编程)的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
目录
类声明放在.h文件中,成员函数定义放在.cpp文件中(用于大的项目)
1、类的出现
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数(实现更加完美遍历的封装)。通过使用类定义自己的数据类型,来反映待解决问题中的各种概念,可以使我们更容易编写、调试和修改程序。
C++中、类的基本思想是数据类型和封装。
- 数据抽象:(依赖于接口和实现分离的编程(以及设计)计术)数据抽象是对实际的事物和概念进行人为描述,抽取所关心的特性,忽略非本质的细节,把这些所关心特性加以描述。
- 封装:(实现类接口和实现的分离)封装后的类隐藏了它的实现细节,使得类的用户只能使用接口而无法访问实现部分。
2、结构体 --> 类
以栈的模拟实现为例:
C语言中:注重过程
C语言中的struct就是结构体,只能定义成员变量。过于零散,且函数名不便于命名。
C语言结构体的定义使用:
在C++中兼容struct语法的使用,同时C++将struct升级成了类。而类可以定义成员变量的同时,还可以定义成员函数。
C++中:注重结果
C++类的定义使用:
(兼容旧的用法)
C++类的调用:
Note:当我们需要在结构体中定义该类型的结构体变量的时候。
C:
typedef struct ListNode //就算是typedef { struct ListNode* a; //也需要写全类型,不能使用typedef后的名字 int val; }LTNode; //要从这行以后,typedef的名字才可以生效。
C++:
struct ListNode //因为struct已经升级成类,所以ListNode是类型也是类名 { ListNode* a; int val; };
虽然,C++可以用struct定义类,但是C++中定义类还是更喜欢使用class。
3、类的定义
class className{// 类体:由成员函数和成员变量组成}; // 一定要注意后面的分号
- class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
- 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
类的访问限定符及封装
【访问限定符说明】
- public(公有)修饰的成员在类外可以直接被访问
- protected(保护)和private(私有)修饰的成员在类外不能直接被访问,只能在类里面访问(此处protected(保护)和private(私有)是类似的)
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- 如果后面没有访问限定符,作用域就到 } 即类结束。
- class的默认访问权限为private(私有),struct为public(公有)(因为struct要兼容C)
//类 -- 其实就是结构,就是除了可以定义变量,还可以定义方法(成员函数)。
class Stack
{
public: //需要允许,类外对成员函数的访问 -- 此限定符定义直到下一个限定符,如若没有即到结尾
//成员函数
void Init()
{
a = 0;
top = capacity = 0;
}
void Push(int n)
{
//……
}
//……
private: //需要禁止,类外对成员变量的访问 -- 此限定符定义直到下一个限定符,如若没有即到结尾
//成员变量
int* a;
int top;
int capacity;
};
此时可以类外对公有可以访问,对私有无法访问。
C++中struct和class的区别
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。(在继承和模板参数列表位置,struct和class也有区别,此文未涉及)
类的两种定义方式
声明和定义全部放在类体中(用于普通的练习)
类声明放在.h文件中,成员函数定义放在.cpp文件中(用于大的项目)
Note:类定义了一个新的作用域,成员函数名前需要加类名然后使用域限定符(::)
意义:方便阅读代码。
编译器的原则:如果要用一个变量或者函数,会先在局部域里面去找,看局部域里面有没有,有就是它,没有就去全局域里面去找,命名空间是不会进去搜索的,除非是命名空间展开或者是指定了命名空间。所以对于类,其是一个域,我们需要指定。
由于其是声明与定义分离,所以其就不是内联了。
4、封装的意义
面向对象的一个重大意义,就是封装:因为封装更规范,更好的管理,写出来的代码是更不容易出错,更容易让人使用的。
封装(封装本质上是一种管理,让用户更方便使用类):隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。隐藏对象内部实现细节,起到对内部数据细节的保护。并且作为使用者,无法如同开发者一样对于代码具有深入的了解。
如,写一个栈:
每个人底层的实现都可能会是不一样的,不同的实现,形式就会导致数据的不同,就如top:
- 先++,后输入,就是所有元素的最后一个。
- 先输入,后++,就是所有元素的最后一个的下一个。
- C语言 一> 没办法封装,规范使用函数访问数据的同时,也可以直接访问数据 一> 不规范
- C++ 一> 封装,必须规范使用函数访问数据,并且不能直接访问数据 一> 更规范
5、类的实例化
用类类型创建对象的过程,称为类的实例化。
class Stack { public: //…… private: int* a; //声明 int top; //声明 int capacity; //声明 };
当要确定变量是声明还是定义时,要看有没有开空间,开空间就是定义,没有开空间就是声明。
- 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它。类就像是设计图,我们可以知道其有多大,可以放多少东西,但是需要实例化出的对象才能实际存储数据,占用物理空间。
6、类对象模型
如何计算类对象的大小
class Number
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
char number;
};
int main()
{
cout << sizeof(Number) << endl;
return 0;
}
正在上传…重新上传取消正在上传…重新上传取消
类并未给成员函数开辟空间,只为成员变量开辟了空间,并且遵循内存对齐。
结构体内存对齐规则
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 每个编译器的默认对齐数不同。
- 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
// 类中仅有成员函数
class A1
{
public:
void f2()
{}
};
// 类中什么都没有---空类
class A2
{};
int main()
{
cout << "A1:" << sizeof(A1) << endl << "A2:" << sizeof(A2) << endl;
return 0;
}
因为未给成员函数开辟空间,所以A1与A2的大小是相同的。
没有成员变量的类对象,给1byte,占位不存储实际数据,标识对象的存在。
存储方式:只保存成员变量,成员函数存在放在公共的代码区。
class A
{
public:
void fun()
{
cout << "fun()" << endl;
}
};
int main()
{
A* pA = nullptr;
pA->fun(); //并不会崩溃或编译错误,而是正常运行。
return 0;
}
call成员函数时,并没有进入类中,而是在编译链接的时候,根据函数名去公共代码区找到函数的地址。fun()前的pA->并没有实际执行的,而是告诉编译器其实在此域的公共代码区中。
7、this指针
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year; // 年 --> (声明)
int _month; // 月 --> (声明)
int _day; // 日 --> (声明)
};
int main()
{
Date d;
d.SetDate(2018, 5, 1);
}
成员函数SetDate并没有传递类进入,只传入了3个变量,并且类中的:年、月、日是声明,并不是定义。那是如何进行对象d的数据输入?这就是因为this指针了。
对于普通的非静态的成员函数(声明于类的成员说明中,不带 static 或 friend 说明符的函数)都会有一个隐藏的参数 一> this指针。
在编译时,编译器会对成员函数分别进行处理,如:
void SetDate(Date* const this, int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}d.SetDate(&d, 2018, 5, 1);
Note:这个是编译器的处理,我们不能这样处理。(语法规定:我们不能传递与接收指针)但是在函数的内部我们可以使用this指针。
this指针的特性
- this指针的类型:* const,即成员函数中,不能更改this指针(对其赋值)。
- 只能在“成员函数”的内部使用。
- this指针是“成员函数”第一个隐含的指针形参,用户不能传递与接收this指针。
- this指针本质上是“成员函数”的形参(因为this指针是形参,所以this指针存储在栈中(但是不一定在栈上,因为可能会有寄存器的优化)),当对象调用成员函数时,将对象地址作为实参传递给this形参。所以,对象中不存储this指针。
由于优化this指针可能存储在寄存中,如VS2019:(是否优化取决于编译器)