在前面对C++有了一个初步的认识,下面便是与C不同的一点。类和对象。在C++里引入了面向对象的编程思想。便有了类和对象。为了方便学习,可对比C语言的结构体。
目录
1. 类与对象的初步认知
1.1 概念
类是现实事务的抽象,如猪,鸡,鸭子,为家禽类。
对象是类的实例化,如老虎是猫科类的实例
1.2 类的定义
在C语言中,结构体不能定义函数,而c++可以。c++里struct用class代替,也可以用struct。
类的定义 :(注意:平时应该在意一下命名规则,形成自己的编程风格。)前面有所介绍:点击进入传送门
class MyClass {
// 成员变量和成员函数
public:
void setData(int data);
private:
int _data;
}; // 注意有分号
class为定义类的关键字,MyClass为类的名字,{}中为类的主体,注意类定义结束时后面分号
1.3 类的访问限定符及封装
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部使用
【访问限定符说明】
- public修饰的成员在类外可以直接被访问
- protected和private修饰的成员在类外不能直接被访问
- protected在后面继承中会继续介绍
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)
1.4 类的作用域
类的所有成员都声明或定义在类的作用域类(简单理解围殴 { } 内即为作用域),当作类外定义成员,需要用::作用域限定符。一般声明放在.h文件中,类的定义放在.cpp文件中,结合后面的类的作用域
// .h
class MyClass {
public:
void setData(int data); // 声明
private:
int _data;
};
// .cpp
#include"class_object.h"
void MyClass::setData(int data) { // 定义
_data= data;
}
// 成员函数的实现最好放在.cpp内,因为可能编译器的优化,会将成员函数内联
1.5 类的实例化
创建一个类,不占实际物理空间,空类的话会占一个字节。但实例化出的对象就要占空间了。比如:家具类,一个类并没有分配空间,但是实例出床,写字台,就会占我们的物理空间。
创建出的实例,我们会通过构造函数来对它进行初始化。
int main() {
MyClass test; // 这个调用的四MyClass()这个默认构造函数
system("pause");
return 0;
}
1.6 类的对象模型
对象模型就是类的成员的布局,类所以引申出类对象的大小。
一个对象的大小,实际就是该类中成员大小之和,当然也要进行内存对齐,内存对齐和C语言的结构体对齐是一个道理。这里都有相关介绍:点击进入传送门
注意,空类比较特殊,编译器给了空类一个字节来唯一标识这个类(带有虚表指针的类除外)
1.7 类成员函数的this指针
类的成员函数其实隐藏了一个参数,就是this指针(类的静态函数除外)。
- this指针的类型:类类型* const
- 只能在“成员函数”的内部使用
- this指针本质上其实是一个成员函数第一个隐含的指针形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
void MyClass::set_data(int data) {
_data = data;
// 相当于
this->_data= data; // 就算没有this,编译器会自动完成
}
2. 类的六个默认成员函数
2.1 构造函数
一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值。即就是初始化对象,并且在对象的生命周期内只调用一次。
其特点归纳如下:
- 函数名与类名相同,无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
- 任意构造函数用户显示定义,编译器都不会再生成,所以若要定义一定要定义一个无参或是全缺省的构造函数。
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1) { //全缺省
_year = year;
_month = month;
_day = day;
}
Date() {} // 无参的构造函数
private:
int _year;
int _month;
int _day;
//const int& ra;
};
// 上面两个函数只能出现一个,并且成为默认构造函数。
// 同时出现,则通过 Date test 类似的方法创建对象时,会产生二义性。
// 因为编译器不知道用哪个构造函数来初始化。
// 当然编译器也会提醒你。
如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
2.2 析构函数
与构造函数功能相反,对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
注意:析构函数不是完成对象的销毁。
特点归纳如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数、无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class String {
public:
String(const char* str = "moren") { // 全缺省的构造函数
_str = (char*)malloc(strlen(str) + 1);
strcpy(str_, str);
}
~String() { // 析构函数
cout << "正在清空资源" << endl;
free(str_);
}
void display() {
cout << str_ << endl;
}
private:
char* _str;
};
2.3 拷贝构造函数
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特点:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
- 若未显示定义,系统生成默认的拷贝构造函数,编译器生成的是浅拷贝
//拷贝构造
Date(const Date& date) {
_year = date._year;
_month = date._month;
_day = date._day;
}
使用传值的方式,如图:
浅拷贝:只拷贝对象本身内容。也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以 当继续对资源进项操作时,就会发生访问违规。
深拷贝:拷贝对象本身内容和对象的资源。如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。
2.4 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
- 函数名字为:关键字operator后面接需要重载的运算符符号。
- 函数原型:返回值类型 operator操作符(参数列表)
bool operator == (Date& d2) {
return _year == d2._year && _month == d2._month && _day == d2._day;
}
Date& operator = (const Date& date) {
if (this != &date) {
_year = date._year;
_month = date._month;
_day = date._day;
}
return *this;
}
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的,操作符有一个默认的形参this,限定为第一个形参
- * 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
- 赋值运算符:只能给已经存在的对象进行复制,改变它的内容,若没有存在则会调拷贝构造函数
2.5 const成员
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
//const 修饰成员函数
void display2() const {
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// …………
//const修饰成员变量
const int ra;
};
问题:
- const对象可以调用非const成员函数吗?
- 非const对象可以调用const成员函数吗?
- const成员函数内可以调用其它的非const成员函数吗?
- 非const成员函数内可以调用其它的const成员函数吗?
const对象不能调非const成员函数,但非const对象能调是const修饰的成员函数。
就好比const修饰的是胖子,瘦子是非const,瘦子能穿胖子的衣服,但胖子穿不了瘦子的衣服。
3. 初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。和赋值顺序相同,和初始化顺序无关
class Date {
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day) { // 这里没有分号
……
}
private:
int _year;
int _month;
int _day;
};
特点:
(a)每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
(b)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
(c)类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 类类型成员(该类没有默认构造函数)
class DiffA {
public:
Diff1(int a)
:_test(a) {
……
}
private:
int _test;
};
class DiffB
{
public:
DiffB(int a, int ref)
:_aobj(a) // 必须初始化
,_ref(ref) // 必须初始化
,_n(10) { // 必须初始化
……
}
private:
Diff1 _aobj; //没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
建议
尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使初始化列表初始化。
4. static成员
4.1 概念:
声明为static的类成员称为类的静态成员,用static修饰的成员变量;称之为静态成员变量;
用static修饰的成员函数,称之为静态成员函数;静态的成员变量一定要在类外进行初始化。
4.2 特性
- 静态成员为所有类对象所共享,不属于某个具体的实例
- 静态成员变量必须在类外定义,定义时不添加static关键字
- 类静态成员即可用类名::静态成员或者对象.静态成员来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值,const修饰符等参数
- 静态成员函数可以调用非静态成员函数吗?
非静态成员函数有this指针,静态成员函数没有,所以调用的时候没有this指针,不能访问非静态的成员函数
- 非静态成员函数可以调用类的静态成员函数吗?
可以
5. 运算符重载:完善class Date类
头文件
#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
void display()
{
cout << year_ << "/" << month_ << "/" << day_ << endl;
}
void judge()
{
cout << "您输入的年份非法,已恢复至默认年份1900/1/1" << endl;
year_ = 1900;
month_ = 1;
day_ = 1;
}
Date(int year, int month, int day);
Date(const Date& d);
Date& operator=(const Date& d);
int getday(int year, int month)
{
static int _day;
int _mon[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
_mon[1] = 29;
}
_day = _mon[month - 1];
return _day;
}
Date operator+=(int days);
Date operator+(int days);
Date operator-=(int days);
Date operator-(int days);
int operator-(const Date& d);
Date& operator++();
Date operator++(int);
Date& operator--();
Date operator--(int);
bool operator>(const Date& d)const;
bool operator>=(const Date& d)const;
bool operator<(const Date& d)const;
bool operator<=(const Date& d)const;
bool operator==(const Date& d)const;
bool operator!=(const Date& d)const;
private:
int year_;
int month_;
int day_;
};
源文件
#include"ClassDate.h"
#include<iostream>
using namespace std;;
Date::Date(int year = 1900, int month = 1, int day = 1)
{
if (year<=0 && month <=0 && month>12)
{
judge();
}
else
{
if(month == 2 )
{
if (!(year % 4 == 0 && year % 100 != 0 || year % 400 == 0) && day > 28)
{
judge();
}
else if ((year % 4 == 0 && year % 100 != 0 || year % 400 == 0) && day > 29)
{
judge();
}
}
year_ = year;
month_ = month;
day_ = day;
}
}
//拷贝构造
Date::Date(const Date& d)
{
year_ = d.year_;
month_ = d.month_;
day_ = d.day_;
}
//重载 赋值=
Date& Date::operator=(const Date& d)
{
year_ = d.year_;
month_ = d.month_;
day_ = d.day_;
return *this;
}
//重载 加 + int
Date Date::operator+=(int days)
{
//Date temp;
if (days < 0)
{
return *this - (-days);
}
this->day_ += days;
while (this->day_ > this->getday(this->year_, this->month_))
{
this->day_ -= this->getday(this->year_, this->month_);
if (this->month_ == 12)
{
++this->year_;
this->month_ =1;
}
else
{
++this->month_;
}
}
return *this;
}
Date Date::operator+(int days)
{
Date ret(*this);
ret += days;
return ret;
}
//重载 减 - int
Date Date::operator-=(int days)
{
if (days < 0)
{
return *this + (-days);
}
this->day_ -= days;
while (this->day_ <= 0)
{
--this->month_;
if (this->month_ <= 0)
{
this->month_ = 12;
}
this->day_ += this->getday(this->year_, this->month_ );
if (this->month_ == 12)
{
--this->year_;
//this->month_ = 12;
}
}
return *this;
}
Date Date::operator-(int days)
{
Date ret(*this);
ret -= days;
return ret;
}
//重载 前自增
Date& Date::operator++()
{
(*this) += 1;
return *this;
}
//重载 后自增
Date Date::operator++(int)
{
Date ret(*this);
(*this) + 1;
return ret;
}
//重载 自减
Date& Date::operator--()
{
(*this) - 1;
return *this;
}
Date Date::operator--(int)
{
Date ret(*this);
(*this) - 1;
return ret;
}
//重载 >
bool Date::operator>(const Date& d)const
{
return this->year_ > d.year_ ||
(this->year_ == d.year_ && this->month_ > d.month_) ||
(this->year_ == d.year_ && this->month_ == d.month_ && this->day_ > d.day_);
}
bool Date::operator>=(const Date& d)const
{
return (*this > d) ||
(this->year_ == d.year_ && this->month_ == d.month_ && this->day_ == d.day_);
}
bool Date::operator<(const Date& d)const
{
return !(*this >= d);
}
bool Date::operator<=(const Date& d)const
{
return !(*this > d);
}
bool Date::operator==(const Date& d)const
{
return (this->year_ == d.year_ && this->month_ == d.month_ && this->day_ == d.day_);
}
bool Date::operator!=(const Date& d)const
{
return !(*this == d);
}
//重载 减 对象
int Date::operator-(const Date& d)
{
int val = 0;
Date _min(*this);
Date _max(d);
int flag = 1;
if (*this > d)
{
_min = d;
_max = *this;
flag = -1;
}
while (_min != _max)
{
++_min;
++val;
}
return val*flag;
}
写在后面:转载请附上链接哦,原创辛苦。欢迎指出错误。
6. 概括本节