目录
1.引入
C语言是面向过程的,但C++是面向对象的,这一点很好地体现在了类上面,什么是类?
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数,这时struct就上升到了类:
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 扩容
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
}
在这个用c++中的类模拟实现的栈中,在类中定义了函数,并且在类中对变量定义的顺序也没有特别规定,c++默认都在一个类中,是一个整体,并且可以在其中定义函数。
2.类的定义
补充:C++中struct和class的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
class Date
{
public:
void Init(int year)
{
_year = year;
}
private:
int _year;//这里加斜杠就是防止形参与这里的变量混淆,以示区分
};
1.用类类型创建对象的过程,称为类的实例化,类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
typedef int DataType;
class Stack
{
public:
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 扩容
_array[_size] = data;
++_size;
}
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s._capacity = 1;
return 0;
}
这里必须先要创建一个栈出来,不能直接对类中的元素赋值,可以理解为类只是一张图纸,什么都没有,只有根据这张图纸盖出房子来,才可以住人。
3.this指针
先来看一段代码的结果:
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 main()
{
Date d1, d2;
d1.Init(2022, 1, 11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
明显这其中的Init函数以及Print函数都是放在公共代码段的,那么为什么会打印出不一样的结果呢?为什么不会发生冲突呢?
这是因为this的作用,C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
// 1.下面程序编译运行结果是?
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
这里打印print()并没有对this解引用,因此即使this指针为空,运行也是正确的!
// 1.下面程序编译运行结果是?
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
这时候就不一样了,将this的空指针传值过去后,对this试图解引用访问_a,这当然是错误的,这时就会报错。
4.默认成员函数
4.1构造函数
如何对一个类进行初始化呢?我们可以在类中写一个init函数,然后完成类的实例化之后,调用类中的初始化函数,在C++11中存在另一种初始化方式;
概念:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。
特性:构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
typedef int DataType;
class Stack
{
public:
Stack(DataType* a, int n)
{
cout << "Stack(DataType* a, int n)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * n);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_array, a, sizeof(DataType) * n);
_capacity = n;
_size = n;
}
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_array[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
DataType Top() { return _array[_size - 1]; }
int Empty() { return 0 == _size; }
int Size() { return _size; }
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_array, newcapacity * sizeof(DataType));
if (temp == NULL)
{
perror("realloc申请空间失败!!!");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
上面这段代码演示了用类来作为构造函数名并且构造函数构成重载。
如果我们在main函数中这样去使用构造函数呢?
int main()
{
Stack s(1);
Stack ss();
return 0;
}
那么第一种就是正确的,而第二种是错误的,原因是:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明,编译器会报警告:
1>\test.cpp(370,8): warning C4930: “Stack ss(void)”: 未调用原型函数(是否是有意用变量定义的?)
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
};
int main()
{
Date d;
d.Print();
return 0;
}
结果却是随机值,这是为什么?我们不写构造函数,那么编译器会自动调用默认构造函数,通常对内置类型不做处理,这在不同的编译器上有着不同的效果,有的可能会主动赋值为0,这里演示的是在vs2022上的处理结果,而自定义类型会去调用它的默认构造。
难道只能让编译器产生随机值吗?C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
例如:
4.2析构函数
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
5.与构造函数类似,当没有显式去写析构函数时,编译器会自动调用析构函数,通常对内置类型不做处理,而对自定义类型会调用析构函数。
总结一下:
1.一般情况下,有动态申请资源,就需要显式写析构函数释放空间。
2.没有动态申请的情况下,不需要些析构。
3.需要释放资源的成员类型是自定义类型时,不需要写析构。
4.3拷贝构造函数
class Date
{
public:
Date(int year = 1900, 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;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
这里是先初始化d1,然后用d1来初始化d2,请注意,拷贝构造函数在本质上也是一种构造函数,构造函数是可以重载的,但这里不能设置为Date d,是因为拷贝构造函数的特性:
1.内置类型会直接赋值拷贝;
2.自定义类型会调用拷贝构造函数;
如果这样,那么传参d1时又会调用一次拷贝构造函数,就这样无限循环递归下去,形成死循环。因此我们显式写的拷贝构造函数就要用引用类型接收。
运算符重载:
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
但如果把运算符重载函数放进类中呢?就需要像这样:
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
这是因为类中的成员函数,会传参this指针,且可以访问到private里的成员变量,所以类中的运算符重载函数有两个变量,一个是this指针,另一个是const修饰的引用类型变量,用const和引用的原因分别是防止传参错误和提高传参效率。(注意这里传参时并没有去调用默认拷贝函数,因为传参类型属于指针,而指针属于内置类型,直接传参)
4.4赋值运算符重载
class Date
{
public:
Date(int year = 2025, int month = 1, int day = 13)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;//这里返回*this并没有错,外部的Date还存在
}
private:
int _year;
int _month;
int _day;
};
这里实现的是三个Date类型的统一初始化。