前言
上篇文章我们一起学习了关于C++的基础入门知识,现在就让我们一起进入C++的核心语法--类与对象
1.什么是类?
class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后⾯分号不能省
略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的⽅法或
者成员函数。
• 为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_
或者 m
开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
• C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是
struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。
• 定义在类⾯的成员函数默认为inline。
人话就是C语言中结构体 struct 的优化版本,我们都知道C语言中struct 只能在结构体中定义成员名,但是C++就NB了,他甚至能在类里面定义函数来使用,struct与class的区别
struct 默认定义为 公有 class 默认为私有
C语言与C++中struct 的区别
C++很爽,使用struct时不必typedef,定义结构体指针时不用重新写struct ,祖师爷让我们减少了struct的复杂
1.2访问限定符
• C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限
选择性的将其接⼝提供给外部的⽤⼾使⽤。
• public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访
问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
• 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有
访问限定符,作⽤域就到 }即类结束。
• class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
访问限定符分为三个:public , private , protected
其中public主要用来定义在类能访问的内容,譬如函数的声明甚至是函数的定义
而private主要来定义成员变量之类的内容
#include <assert.h>
#include <iostream>
using namespace std;
//类的定义形式,相当于C预言中的struct但是更加全面,甚至可以把函数直接定义在class里面
class Stack//class后面定义的为类的名字
{//访问限定符public protected private
// public:公有 protected 私有 private 保护
//从public开始到下一个限定符 是公有资源,即使在类外面也可以被访问
//一般我们会在Public中定义函数
// 如果想要在类里面声明 在类外面定义 需要用命名域来找到;
// private 和 protected只能在class里被使用
//
// 访问权限是从该访问限定符出现 到 下一个访问限定符 之间的区域里
//
// 目前阶段protected和private都是私有的情况,以后继承章节才会体现出他们之间的区别
// 若访问限定符则默认为private
// 成员变量一般定义在private里
//class定义
public:
// 成员函数
void Init(int n = 4)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
capacity = n;
top = 0;
}
void Push(int x)
{
// ...扩容
array[top++] = x;
}
int Top()
{
assert(top > 0);
return array[top - 1];
}
void Destroy()
{
free(array);
array = nullptr;
top = capacity = 0;
}
private:
// 成员变量
int* array;
size_t capacity;
size_t top;
};
2.类域
就是类里面用括号括起来的内容,假设你想要把声明写在类里面,把函数的定义写在类的外面,则需要用 类名::(类里定义的)函数名(参数)写下来
// 声明和定义分离,需要指定类域
void Stack::Init(int n)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申请空间失败");
return;
}
因为如果你不指定是Stack里的Init,编译器就会将Init当成全局变量,从而导致找不到Init
3.实例化
大家还记得C语言中是怎么存储数据类型的吗
内存对齐规则:
• 第⼀个成员在与结构体偏移量为0的地址处。
• 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
• 注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。
• VS中默认的对⻬数为8
• 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
• 如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩
就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
为啥要内存对齐?
利用空间来换取时间
// 计算⼀下A/B/C实例化的对象是多⼤?
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
这样的话计算机只需要一次就能拿出_i,虽然浪费了3个空间,但是节约了时间成本
如果类里面没有成员对象,那存储起来有多少个字节呢,为了有用,编译器还是给了他一个字节来证明其存在
4.this指针
C++里面默认传参时的指针,祖师爷会将每个单独实例化的变量的指针,隐形的传给函数。
#include<iostream>
using namespace std;
class Date
{
public:
// void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day)
{
// 编译报错:error C2106: “=”: 左操作数必须为左值
// this = nullptr;
// this->_year = year;
_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 这⾥只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
// Date类实例化出对象d1和d2
Date d1;
Date d2;
// d1.Init(&d1, 2024, 3, 31);
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}
5.默认构造函数
就是如果你不写,系统也会给一个默认的,但可能并不符合你的需求
5.1构造函数
//1.构造函数: 相当于实现栈与队列时我们使用的初始化Init功能,而且可以自动调用
//特点:1.函数名与类名相同 2. 对象实例化后会自动调用 3.无返回值且不需要写void
//4.可以重载
#include <iostream>
using namespace std;
class Date
{
public:
//Date()//无参构造函数
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
////2.带参构造函数
//Date(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}这三种构造函数出现一种即可
//全缺省构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private://声明对象
int _year;
int _month;
int _day;
};
int main()
{
//不需要初始化
Date d1;//将对象实例化,无参的不能加括号,因为分不清楚函数声明还是实例化
d1.Print();
Date d2(2024, 7, 16);
d2.Print();
}
5.2拷贝构造函数
拷贝构造函数相当于构造函数的重载
拷贝构造函数的第一个参数必须得是引用传参,如果直接用类对象传值则会进入无限递归
最好写成const 类名& 来传参
因为C++规定传值传参需要调用拷贝构造,拷贝构造要先传参,所以如果是传值传参则会一直递归
如果是引用的话,由于是别名,所以不会需要拷贝构造
自定义类型(含有指针指向资源)一定会有拷贝构造,根据情况自行写拷贝构造函数,计算机自动形成的浅拷贝并不满足我们的需求 例如栈的深拷贝
5.3析构函数
/析构函数相当于实现栈与队列时我们使用的初始化Destroy功能,而且可以自动调用
//特点:1.函数名是类名前面加上‘~’ 2. 对象实例化后会自动调用 3.无返回值且不需要写void
//4.对象生命周期结束时,系统会自动调用默认析构函数
class Stack
{
public:
Stack(int n = 4)
{
Stack* tmp = (Stack*)malloc(sizeof(Stack) * n);
if (tmp == nullptr)
{
perror("malloc fail!!!");
return;
}
_a = tmp;
_capacity = n;
_top = 0;
}
~Stack()
{
free(_a);
_capacity = 0;
_top = 0;
}
private:
Stack* _a;
int _capacity;
int _top;
};
//利用两个栈来实现队列
class MyQueue//MyQueue不需要构造函数,和析构函数,因为Stack里已经对MyQueue实现俩函数
{
public://编译器将默认生成MyQueue的析构 = Stack的析构,通过调用Stack析构函数来实现MyQueue
private:
Stack Mq1;
Stack Mq2;
};