👀先看这里👈 😀作者:江不平 📖博客:江不平的博客 📕学如逆水行舟,不进则退 🎉欢迎关注🔎点赞👍收藏??留言📝 ?本人水平有限,如果发现有错误的地方希望可以告诉我,共同进步👍
🏐1.什么是类
C语言是面向过程的,关注的是过程 C++是基于面向对象的,关注的是对象(注意我们这里是基于面向对象,不是纯面向对象,兼容C语言。 我们关注的这些对象肯定在运用时会有一定的分类,自然的,C++中的类就出现了。
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数。例如实现栈时写的函数都可写到栈中去
typedef int DataType;
struct Stack
{
void Init(int newcapacity = 4)
{
_a = (DataType*)malloc(sizeof(DataType) * newcapacity);
_size = 0;
_capacity = newcapacity;
}
DataType* _a;
int _size;
int _capacity;
};
C++中我们将struct进行了升级,不仅可以定义函数,还可以自定义类型,C++我们用class来代替struct,避免使用typedef,提高了效率。需要注意的是class中若没有定义,则默认属性为private,如果是struct就是public,从这点上来看,二者不是无差别替换。
🏀1.1类的结构
class className
{
};
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。 类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
🏀1.2类的两种定义方式
声明和定义都在类体中
struct QueueNode
{
QueueNode* next;
int val;
};
class Queue
{
public:
inline void Init()
{
}
private:
QueueNode* head;
QueueNode* tail;
};
声明和定义分离,声明在.h 文件,定义在.cpp文件
声明
struct QueueNode
{
QueueNode* next;
int val;
};
class Queue
{
public:
void Init();
void Push(int x);
void Pop();
private:
QueueNode* head;
QueueNode* tail;
};
定义
void Queue::Init()
{
head = tail = nullptr;
}
void Queue::Push(int x)
{}
void Queue::Pop()
{}
这里的声明和定义指的是成员函数,需要注意的是分离后不能跟之前一样直接去写定义,需要访问类,函数名前要加访问限定符 了解了类的定义和inline的相关内容(inline不允许声明和定义分离)后,我们可以得到对于小的函数我们可以直接在类中展开,对于大的函数我们将其声明和定义分离最好
🏐2.类的作用域和实例化
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。 用类类型创建对象的过程,称为类的实例化
-
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它; -
一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
🏐3.关于this指针
using namespace std;
class Date
{
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 a;
};
int main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
我们会在执行上面代码时发出这样的疑问,我们用d1调用了类中的函数,可是函数怎么知道它要在哪个对象上执行操作?我们没有传多余的参数啊,那它是怎么知道的呢,这里就引出来this指针。 C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。 也就是说是通过this指针来让函数找到该对哪个对象进行操作,代码可以看成如下
class Date
{
public:
所以我们只能写成下面这样,上面那个供我们理解所用
void Init(int year, int month, int day)
{
cout << this << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print(Date* const this)
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
int a;
};
int main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
this指针的特性
- this指针的类型:类型* const,即成员函数中,不能给this指针赋值。
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
this指针存储在栈上,因为它是个形参
🏐4.类的默认成员函数
如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。 默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
🏀4.1构造函数
我们经常会忘记初始化,可能会导致崩溃或出现随机值的情况,有没有一种方式能保证对象一定初始化呢?构造函数可以解决初始化的问题,是初始化!!不是定义!!开空间不是它来做。
?4.1.1概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
?4.1.2特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。 特性:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载
class Date
{
public:
Date(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;
};
注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
给缺省值的构造函数,尤其是全缺省的构造函数最好用,两种给缺省的方法,如下:一种在形参给缺省值,一种在对象声明时给缺省值
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
有哪些是默认构造函数呢,我们不需要传参数的就是默认构造函数
- 编译器自己生成的
- 我们写的无参默认构造函数
- 我们写的全缺省的默认构造函数
这三种我们都不需要传参。
注意这里无参和全缺省的默认构造函数同时存在语法上是没有问题的,但是在调用的时候会有歧义,二义性。 所以我们三个默认构造函数我们一般都只写一个
🏀4.2析构函数
?4.2.1概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
?4.2.2特性
析构函数是特殊的成员函数,特性:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
析构函数和构造函数一样,不是每次都要写,看需求:
class date
{
public:
date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
};
typedef int datatype;
class stack
{
public:
stack(size_t capacity = 3)
{
_array = (datatype*)malloc(sizeof(datatype)* capacity);
if (Null == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void push(datatype data)
{
_array[_size] = data;
_size++;
}
~stack()
{
cout << "~stack()->" << _array << endl;
free(_array);
_capacity = _size = 0;
_array = nullptr;
}
private:
datatype* _array;
int _capacity;
int _size;
};
class myqueue {
public:
void push(int x) {}
private:
size_t _size = 0;
stack _st1;
stack _st2;
};
void func()
{
date d;
stack st;
myqueue q;
}
int main()
{
func();
return 0;
}
第一个日期类不需要写,没什么需要释放的,第二个类需要显示写,否则会内存泄漏,第三个类默认生成的就够用,也不用显示写,会调用第二个类的析构函数。
🏀4.3拷贝构造函数
?4.3.1概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类型对象创建新对象时由编译器自动调用。
?4.3.2特性
拷贝构造函数也是特殊的成员函数,特性:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
void func()
{
Date d1(2022, 7, 23);
Date d2(d1);
}
int main()
{
func();
return 0;
}
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
我们会发现对拷贝后的对象进行修改时会影响到第一个对象,并且会析构两次,程序崩溃。这也就是所说的浅拷贝,想要解决这个问题我们要进行深拷贝。
🏐一些小点
- 野指针没有人能访问到就没有危害,出于好习惯要置空
- 构造和析构是自动调用,不是自动生成,构造动态开辟空间时都要显示写析构进行free
- 默认构造函数没有参数,就没有函数重载,对应的也就只有一个析构函数
- 指针都是内置类型
- 你不知道指针是否fopen来的还是new来的,甚至有些指针不能处理,这也是析构不能处理内置类型的原因之一
这就是类和对象的一部分内容了,觉得还不错的铁汁点赞收藏一下吧😀
|