一丶类和对象
1.引入
C++相较于C语言引入了类的概念,类和结构体看起来很像,但实际应用大不相同。
class A{ //class + 类名
//...
//类体
};
以上我们创建了一个A类型,其中包含三种访问权限:public,共有的,可以被外部访问;protect,受保护的,不能被外部访问,在继承那块内容使用较多;private,私有的,不能被外部访问。这样的A类型就是一个类,接下来我会用一个Date类来讲解有关内容。
2.实例
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
/*
Date() //普通构造函数
{
//...
}
*/
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
(1).实例化
创建对象与结构体很像,但C++不再依赖typedef 和 struct创建一个对象,可以直接Date d;
(2).访问
访问一个对象的成员,如果是实例对象,就用“ . ” 访问,比如 d._year (私有不可直接访问,这里举个例子),如果是指针,就用“ -> ”访问,Date *ptr = &d, ptr->_year,如果是引用也用“ . ”。
(3).大小
结构体用sizeof可以计算出它的大小,同样我们用sizeof(Date)可以计算出Date类的大小,如果是像我上面那样的话,结果应该是12,这里就有疑问了,因为Date类不是还有函数吗?函数
难道不占空间吗?实际上一个类的大小,实际就是该类中”成员变量”之和,当然要注意内存对齐,
然后在一个公共代码区存放成员函数。
(4).对齐规则
①. 第一个成员在与结构体偏移量为0的地址处。
②. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
③. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
④. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
(5).this指针
void Init(int year, int month, int day) //两个都是类成员函数
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
在对象d1 和 d2 都调用 Init 和 Print两个函数时,根据函数实现的内容来看,函数体中没有关于不同对象的区分,但能准确初始化和输出d1和d2。是因为每个对象都有一个隐藏的this指针,这个指针只能在自己的对象内使用,指针指向的就是这个对象的地址。C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
二丶构造函数和析构函数
1.构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。在下面代码中,我展示了两个构造函数,是可以同时存在的,这属于构造函数的重载,后面的内容会讲解,如果不写构造函数,编译器会自动调用默认构造函数,默认构造函数什么也不会做。如果我们 Date d1;这样创建一个对象,就会调用第一个构造函数,如果 Date d2(2024,1,31);编译器会调用第二个构造函数。
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
Date() //普通构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
2.拷贝构造函数
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
Date() //普通构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_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;
};
//...
Date d1;
Date d2(d1); //拷贝构造函数
Date d3 = d2; //拷贝构造函数
上述代码就实现了拷贝构造函数,是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。其中d2 和 d3 实际上是不存在的,都是用已有的对象来初始化,这个过程终究是调用的构造函数来初始化对象。当然这里有一个疑点就是最后一行代码,待会再说。
3.析构函数
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。形式如下。析构函数只能有一个,无参数无返回类型,不能重载。申请了空间最好还是写析构函数。
~Date()
{
cout<<"析构函数"<<endl; //这里要做什么随意
}
三丶重载函数
上面已经提到了构造函数的重载,接下来我们重点讲解运算符重载。我们知道两个整型变量a 和 b可以直接相加,但如果我想Date 类型加上一个整型改变时间,可以写这样一个函数,但每次调用略显麻烦,于是我们有了另外一种方法。
void AddDate(Date &d, int day)
{
//...这里有部分代码省略了,假如_day 是公有的
d._day += day;
}
第二种办法,这样就可以 d1 + 10,天数加10天,这样实际是 d1.operator+(10),也是调用了一个函数,但要简单太多了,除了 + ,还有其他运算符都可以重载。
其中不能被重载的运算符有:
1.作用域操作符: ::
2.条件操作符: ?:
3.点操作符: .
4.指向成员操作的指针操作符:->* , . *
5.预处理符号:#
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
Date() //普通构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//日期加天数
Date operator+(int day);
//可以在类内定义
private:
int _year;
int _month;
int _day;
};
//类外定义 :: 前面提到的域作用符
// 返回类型 类名::函数名()
Date Date::operator+(int day)
{
//...
}
大多数运算符重载都是如此,有两个目前来说是特殊的就是流提取 >> 和 流插入 <<,以及++和--运算符。这里我就直接以代码的形式展现了。
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month); //可以在类的内部定义 也可以在外部定义
Date() //普通构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//日期加天数
Date operator+(int day);
//可以在类内定义
// 前置++
Date& operator++()
{
*this += 1; //假设 +=以及完成重载
return *this;
}
// 后置++
Date operator++(int) //这个int没用但是必须写,为了区分,别问为什么
{
Date tmp = *this;
*this += 1;
return tmp;
}
friend ostream& operator<<(ostream& out,const Date d) //这是友元类
{
out << d._year << ":" << d._month << ":" << d._day << endl;
return out;
}
friend istream& operator>>(ostream& in,const Date d);
private:
int _year;
int _month;
int _day;
};
//类外定义 :: 前面提到的域作用符
// 返回类型 类名::函数名()
Date Date::operator+(int day)
{
//...
}
由于 << 和 >> 是二元运算符,它们需要两个操作数:一个左操作数和一个右操作数。这意味着它们的重载版本需要两个参数:一个表示流对象(如 std::ostream 或 std::istream),另一个表示我们想要插入或提取的自定义对象。如果我们将它们定义为类的成员函数,那么它们将只有一个隐式的 this 参数来表示类的实例,而缺少另一个参数来表示流对象。因此,为了使 << 和 >> 运算符能够与自定义类型一起使用,我们需要将它们定义为类的友元函数。这样,它们就可以访问类的私有和保护成员,并且可以接受两个参数:一个流对象和一个自定义对象。
为了能够连续输入或者输出,我们在函数定义的返回的是ostream 或 istream 类型的对象,
比如(cout << d1 )<< d2,这里cout << d1 是 ostrea( cout , d1) 然后返回cout ,然后就是 cout << d2。
本文详细介绍了C++中类和对象的基础概念,包括访问权限、实例化、构造函数(普通和拷贝)、析构函数的作用以及运算符重载,特别是对复制构造函数和流操作符的特殊处理。
930

被折叠的 条评论
为什么被折叠?



