目录
利用构造函数对类对象进行初始化
1、对象的初始化
不可以在声明成员变量的时候对数据成员初始化:因为类不是实体,是一个抽象类型,不占用内存空间,无法容纳数据。
当把数据成员声明为public时,可以用花括号“{}”,顺序列出各个公有成员的值,两个值用逗号隔开
上述的对象初始化类似于结构体
2、构造函数实现数据成员的初始化
构造函数:是一个特殊的成员函数,不需要用户调用,在建立对象时自动执行
构造函数的一些说明:
- 定义对象是自动调用构造函数,而且只能执行一次,构造函数一般是public
解释说明:在建立类对象时,系统为该对象分配存储单元,此时执行构造函数,然后把指定的初始值送入到有关数据成员的存储单元中- 构造函数没有返回值,它的作用只是对对象进行初始化
- 构造函数不能被用户调用
- 可以用一个类对象初始化另一个对象,如
class Time time1;
class Time time2 = time1;- 构造函数不仅可以对数据成员赋值,而且可以包含其他语句,如,cout等,
但是一般不提倡在构造函数中加入与初始化无关的内容- 如果用户自己没有定义构造函数,则C++系统会自动生成一个构造函数,只是函数体为空,参数为空,不执行初始化
3、带参数的构造函数
构造函数名(类型1 参数1,类型2 参数2 ,。。。)
类名 对象名(实参1,实参2,。。。);
在定义对象时给出实参,传递给构造函数的形参中,实现对象的初始化。
//类声明头文件
Box(int ,int ,int );
//类实现文件
Box::Box(int length,int width,int height){
this->length = length;
this->width = width;
this->height = height;
}
//主函数文件
class Box box1(1,2,3);
4、带参数初始化表对数据成员初始化
参数初始化表实现对数据成员的初始化。
类名::构造函数名([参数表])[:成员初始化表]
{
[函数体]
}
[] 表示可有可无
//类声明头文件
Box(int ,int ,int);
//类定义文件
Box::Box(int len,int w,int h):length(len),width(w),height(h){}
//主函数文件
class Box box1(1,2,3);
5、构造函数的重载
构造函数的重载:在一个类中定义多个构造函数,这些构造函数具有相同的名字,参数的个数或参数的类型不相同
//类声明的头文件
Box();
Box(int);
Box(int ,int ,int);
//类实现文件
//无参的构造函数
Box::Box()
{
this->length = 10;
this->width = 10;
this->height = 10;
}
//有参的构造函数
Box::Box(int h):height(h){
this->length = 10;
this->width = 10;
}
//有参的构造函数
Box::Box(int len,int w,int h):length(len),width(w),height(h){}
//主函数文件
class Box box1;
cout<<box1.volume()<<endl;
class Box box2(5);
cout<<box2.volume()<<endl;
class Box box3(1,2,3);
cout<<box3.volume()<<endl;
说明:
- 建立对象时不必给出实参的构造函数为默认构造函数
- 建立对象时选用无参的构造函数时,应当注意:
class Box box1; //没有括号 - 尽管一个类中有多个构造函数,但是对每一个对象来说,只执行一个构造函数
6、使用默认参数的构造函数
1、声明有默认参数的构造函数时,形参名可以省略
//以下两种均可
Box(int len = 5,int w = 5,int h = 5);
Box(int = 5,int = 5,int = 5);
2、如果构造函数的全部参数都指定了默认值,则在定义对象是可以给出一个或几个实参,也可以不给出实参
如果构造函数的全部参数都指定了默认值:默认构造函数
一个类中只能有一个默认构造函数
注意:以下代码中给出了两个默认的构造函数,是错误的;
在调用时出错:对重载函数的调用不明确
//类声明文件
Box();
Box(int = 5,int = 5,int = 5);
3、如果构造函数的全部参数都指定了默认值,不能再定义重载构造函数
总结:一般不同时使用默认参数的构造函数和构造函数的重载
析构函数进行清理工作
析构函数:在对象生命期结束时,自动执行析构函数
没有返回值,没有函数类型,没有函数参数,不能重载
在一个类中可以有多个构造函数,但是只有一个析构函数
当对象声明周期结束,自动执行析构函数,具体一下集中情况:
- 在一个函数中定义了对象(自动局部对象),函数被调用结束,对象应该释放,在对象释放前自动执行析构函数
- 使用类名定义一个临时对象,临时对象的作用域仅仅在这一行,先使用,之后直接析构
- 静态局部对象在函数调用结束时对象也不释放,因此也不调用析构函数,只有main函数结束或exit函数结束时,才调用static局部对象的析构函数
- 如果定义了一个全局对象,在main函数结束或exit函数结束时,才调用全局对象的析构函数
- 如果使用new运算符动态的简历一个对象,当用delete运算符释放该对象是,先调用该对象的析构函数
析构函数的作用:
- 释放资源
- 对象结束之后的操作,如输出有关信息
举例说明:在下面定义中,使用了成员函数(指针),在析构函数中delete ptr,同时给出了回执信息
//类定义头文件
public:
Box(int = 5,int = 5,int = 5);
~Box();
private:
int *ptr;
//类实现文件
Box::Box(int len,int w,int h):length(len),width(w),height(h)
{
this->ptr = new int(10);
}
Box::~Box()
{
delete ptr;
cout<<"调用了析构函数"<<endl;
}
调用构造函数和析构函数的顺序:先构造后析构,后构造先析构。类似于栈,先进后出
对象数组
建立对象数组时,需要调用构造函数,如果有n个元素,则调用n次构造函数
class Box box[10];中需要调用10次构造函数
对象数组的定义:
- 如果构造函数只有一个参数,则可以在花括号“{}”内提供实参,如Age age[3] = {10, 20, 10};
- 如果构造函数有多个参数,不可以向上述那样。否则,编译系统会根据花括号“{}”内的数据,给每一个对象元素的构造函数传递第1个实参。
如,类Box的构造函数中有3个参数,
class Box box[3] = {10,20,15};表达的意思是:box[0]的第一个实参是10.box[1]的第一个实参是20. box[2]的第一个实参是15.
如果class Box box[3] = {10,20,15,30};直接报错:实参个数不能超过数组元素的个数,此时 4 > 3
3.对象数组的正确定义:
class Box box[3] = {Box(10,20,15),Box(20,20,15),Box(10,15,15)};
对象指针
1、指向对象的指针
对象的指针:一个对象存储空间的起始地址
类名 *对象指针名 class Box *ptr
2、指向对象成员的指针
1)、指向对象数据成员的指针变量
数据类型名 *指针变量名
int *ptr;
ptr = &box1.height;
2)、指向对象成员函数的指针变量
插入函数基础知识:
函数:int fun(int,int,int){},
函数的地址:fun或&fun
函数的类型 :int (int ,int ,int)
函数的类型 = 函数声明-函数名;
函数的类型 = 函数参数的类型 + 函数参数的个数 + 函数的返回值类型
定义函数指针:
int (*ptrFunc)(int,int,int); ptrFunc = &fun; cout<<ptrFunc(5,8,9)<<endl;
定义指向公共函数成员的指针变量:
数据类型名 (类名::*指针变量名)(参数列表)
指针变量明 = &类名::成员函数名
int (Box::*ptrFunc)();
ptrFunc = &Box::volume;
class Box box;
cout<<(box.*ptrFunc)()<<endl;
在代码的第二行有取地址符(&)和第四行有指针运算符(*)应该相互呼应。
同时优先级 () > *,所以第四行需要添加括号(box.*ptrFunc),之后再和后面的括号()结合
3、指向当前对象的this指针
this指针:指向当前对象,保存当前对象的地址
类型:类名 *
类成员函数的形参列表中的第一个参数(隐含的参数)
int Box::volume(){ return this->length * this->width * this->height; } C++编译器处理为: int Box::volume(Box *this){ return this->length * this->width * this->height; }
先有对象,然后有this指针
编译器的调用过程:box.volume(&box) , this = &box,然后才是成员变量或函数成员
this指针比较特殊的使用方式:
返回当前对象的地址和返回当前的对象
//类声明文件 Box* getAddr();//返回当前对象的地址 Box getSelf();//返回当前对象 //类实现文件 Box* Box::getAddr()//返回当前对象的地址 { return this; } Box Box::getSelf()//返回当前对象 { return *this; } //主函数 class Box * ptr; class Box box; ptr = box.getAddr(); cout<<ptr<<" "<<&box<<endl; box = box.getSelf();
公用数据保护
1、常对象
类名 const 对象名[(参数列表)] 或者 const 类名 对象名[(参数列表)]
2、常对象成员
1)常数据成员
const 类型 变量名
常数据成员只能通过参数初始化列表实现初始化
const int hour
Time::Time(int h):hour(h){}
2)常成员函数
类型名 函数名(参数表)const
//类声明文件
int volume() const;
//类实现文件
int Box::volume() const{
return this->length * this->width * this->height;
}
//main函数
class Box box;
cout<<box.volume()<<endl;
总结:
常对象只能访问常成员函数
常成员函数只能引用本类中的数据成员,而不能修改他们
常成员函数不能调用非const成员函数
3、指向对象的常指针
类名 * const 指针变量名
解释:将指针变量声明为const类型, 这样的指针变量始终保持初值,不可改变,始终保持指向同一个对象,但是可以改变其指向对象的值;
class Box box1;
class Box* const ptr = &box1; //ptr始终如一,不可改变指向
cout<<ptr->volume()<<endl;
box1 = Box(2,5,8); //改变了其指向对象的值,使用了临时对象,所以调用了析构
cout<<ptr->volume()<<endl;
作用:通常使用常指针为形参,保证在函数执行过程中不改变指针变量的值,使其始终指向原来的对象
4、指向常对象的指针变量
1、指向常变量的指针变量
常变量的定义:const 类型名 变量名
指向常变量的指针变量:const 类型名* 指针变量名
1、如果一个变量为常变量,那么只能用指向常变量的指针变量指向它
2、指向常变量的指针变量也可以指向非const的变量,不能通过指针变量来改变指向变量的值
3、函数的形参是普通(非const)的指针变量,实参只能用指向普通的指针变量,而不能用const变量的指针
2、指向常对象的指针变量
常对象的定义:const 类名 对象名([参数表])
指向常对象的指针变量:const 类名 *指针变量明
1、如果一个对象为常对象,那么只能用指向常变量的指针变量指向它
2、指向常对象的指针变量也可以指向非const的对象,不能通过指针变量来改变指向对象的值,但是指针变量本身的值可以修改
const int a1 = 8; const int *ptrInt = &a1; cout<<*ptrInt<<endl; int a2 = 10; ptrInt = &a2; cout<<*ptrInt<<endl;
3、指向常对象的指针通常用于函数的形参,保证形参所指向的对象,使其在函数执行过程中不被修改
一般是show ,display()等函数。
上述的结论是:若希望在调用函数时对象的值不被修改,应把形参定义为指向常对象的指针变量,同时用对象的地址为实参(对象可以是const和非const型)
5、对象的常引用
SVVVIP:
对象的常引用和常指针作为函数参数,在保证数据安全,不被随意修改,同时不必建立实参的拷贝,提高了程序的运行效率。
常对象:const 类名 对象名([实参表]) 定义时必须初始化 指向常对象只能是常指针
常指针:const 类名 *ptr 可以指向const对象和非const对象
常引用:const 类名 &引用名 可以引用const对象和非const对象
常函数:返回值类型 函数名([参数表]) const 可以引用数据成员,但不可修改;且不可调用非const函数
常数据:const 类型 变量名 只能通过参数初始化列表实现初始化
对象的动态建立和释放
采用new和delete,实现动态建立和释放。
第一行代码:建立对象,初始化,
第二行代码:释放对象,释放空间之前会自动调用析构函数(delete语句会触发析构函数)
class Box *ptr = new Box(12,5,16);
delete ptr;
对象的赋值和复制
1、对象的赋值
对象的赋值:一个对象中数据成员的值赋给另一个对象数据成员
对象的赋值的原理是赋值运算符的重载
- 对象的赋值只对其中数据成员(非静态数据成员)的赋值,而不是对成员函数的赋值。
- 类的数据成员中不能包括动态分配的数据(用new/malloc时开辟,delete/free时释放),否则赋值时可能出现严重后果(容易出现野指针)
2、对象的复制
类名 对象2(对象1) class Box box2(box1);
类名 对象2 = 对象1 class Box box2 = box1;
复制构造函数(拷贝构造函数):只有一个参数,在进行对象复制时,主动调用拷贝构造函数,而不去调用构造函数
Box(const Box &box);
构造函数和拷贝构造函数的区别:
1)、形式上:类名(形参列表)
类名(类名 & 对象名)
2)、建立对象时,实参类型不同。 Box box1(12,13,45);
Box box1(&box2);
3)、调用的时间点
构造函数在对象建立的时候被调用
拷贝构造函数在用已有的一个对象复制另外一个新对象时被调用,具体分为三种情况
- 程序建立一个新的对象,需要用另一个对象去初始化
- 当函数的参数为类的对象时(普通的形参,不是引用,不是指针)
- 函数的返回值为类的对象时(因为在函数结束的时候,需要将返回值带回函数调用处,但是函数中的对象的作用域已经结束并释放,这时需要将函数中的对象复制(调用拷贝构造函数)一个临时对象并传递给函数的调用处)
静态成员
静态数据成员
1、静态数据成员不属于某个对象的成员,在所有对象之外开辟空间。
2、在一个函数中定义了静态变量,在函数结束时该静态变量并不释放,仍然存在并保留其值;
在一个类中定义了静态数据成员,在程序编译时被分配空间,到程序结束时才释放空间
3、在类体中定义静态成员变量 static 类型 静态成员名
3、静态数据成员只能在类体外初始化 类型 类名::静态成员名 = 初值 类体中用static定义,类体外不用添加static
int Box::radius = 10; 不可以用参数初始化表对静态数据成员初始化
4、静态数据成既可以通过对象名引用,也可以通过类名引用 box1.radius 或者 Box::radius
5、如果静态数据成员为private时,则不能通过类名引用,只能通过公用的成员函数引用
6、静态数据成员的作用域仅限于类的作用域(如果在一个函数中定义了一个类,那么静态数据成员的作用域在此函数中)
static成员必须在类外进行初始化(初始化格式: int Box::radius = 10;),而不能在构造函数内进行初始化,不过也可以用const修饰static数据成员在类内初始化 。因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
静态成员函数
1、静态成员函数是类的一部分而不是对象的一部分,使这个类只存在这一份函数,所有对象共享该函数,不含this指针
2、static声明静态成员函数 static 类型 函数名([参数列表])
3、静态成员数据的定义 类型 类名::函数名([参数列表])
4、因为静态成员函数不属于某个对象,所以为静态成员函数没有this指针
5、静态成员函数主要访问静态数据成员,而不访问非静态成员
(因为没有this指针,不知道非静态成员属于哪个对象的this指针)
6、如果静态成员函数想访问非静态成员,需要在形参中传入对象(常引用,常指针),使得非静态成员属于那个对象,并用 对象名和成员运算符(box.height)
7、当static成员函数在类中声明时使用static修饰,而类外定义时不需要加static修饰符。
8.、上述7和inline正好相反,inline在类声明,在类外定义时需要加inline修饰符
结论:C++只用静态成员函数引用静态成员,而不引用非静态数据成员。
友元
1、友元函数
在类体中用friend声明,在类体外定义且没有用类名限定——>此函数是本类的友元函数。
解释下列代码:
display函数是Time类中friend函数,所以display函数可以访问私有成员
display函数的定义没有被Time限定,所以display函数不是Time类的成员函数,没有this指针,不可以默认引用,必须指明访问的对象(也就是作为形参的常引用)
//类声明文件
#include<iostream>
using namespace std;
class Time
{
public:
Time(int,int,int);
friend void display(const Time &);
private:
int hour;
int min;
int sec;
};
//类实现文件
#include<iostream>
#include "time.h"
using namespace std;
Time::Time(int h,int m,int s):hour(h),min(m),sec(s){}
void display(const Time &time)
{
cout<<time.hour<<":"<<time.min<<":"<<time.sec<<endl;
}
//主函数文件
#include<iostream>
#include "time.h"
using namespace std;
int main(){
class Time time(22,25,30);
display(time);
system("pause");
return 0;
}
2、友元成员函数
friend函数可以为一般函数,而且也可以是另一个类的成员函数
类模板
即使是在创建非内联函数定义时,我们还是通常想把模板的所有声明都放入一个头文件中。这似乎违背了通常的头文件规则:“不要放置分配存储空间的任何东西“(这条规矩是为了防止在连接期间的多重定义错误),但模板定义很特殊。由template<…> 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
类模板是类的抽象,类是类模板的实例
类模板声明:
template <typename T> class Compare { public: Compare(T ,T ); T max(); T min(); private: T x; T y; };
类模板定义:
类模板定义外定义成员函数:
template <typename 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表){.......}
template <typename T> Compare<T>::Compare(T a,T b):x(a),y(b){} template <typename T> T Compare<T>::max(){ if(this->x > this->y) return this->x; else return this->y; } template <typename T> T Compare<T>::min(){ if(this->x < this->y) return this->x; else return this->y; }
类模板中有多个类型,且还有默认值:
只需要在类模板中使用默认模板参数,必须从右向左连续赋值默认类型
sing namespace std; template <typename T,typename Y = int> class Compare { public: Compare(T ,Y ); T max(); T min(); private: T x; Y y; }; template <typename T,typename Y> Compare<T,Y>::Compare(T a,Y b):x(a),y(b){} template <typename T,typename Y> T Compare<T,Y>::max(){ if(this->x > this->y) return this->x; else return this->y; } template <typename T,typename Y> T Compare<T,Y>::min(){ if(this->x < this->y) return this->x; else return this->y; }
类模板定义对象:
类模板名 <实际类型名> 对象名;
类模板名 <实际类型名> 对象名(实参表);
class Compare<int> compare(5,40); //可以省略一个,有了默认 class Compare<int , char> compare(5,40); //覆盖默认 class Compare<int> * ptr = new Compare<int>(5,40); //可以省略一个,有了默认 class Compare<int,int> * ptr = new Compare<int,int>(5,40); //覆盖默认
总程序:
//模板类声明与定义,必须同时放在头文件中 #include<iostream> using namespace std; template <typename T,typename Y = int> class Compare { public: Compare(T ,Y ); T max(); T min(); private: T x; Y y; }; template <typename T,typename Y> Compare<T,Y>::Compare(T a,Y b):x(a),y(b){} template <typename T,typename Y> T Compare<T,Y>::max(){ if(this->x > this->y) return this->x; else return this->y; } template <typename T,typename Y> T Compare<T,Y>::min(){ if(this->x < this->y) return this->x; else return this->y; } //主函数文件 #include<iostream> #include "compare.h" using namespace std; int main() { class Compare<int> compare(5,40); //class Compare<int,int> compare(5,40); class Compare<int,int> * ptr = new Compare<int,int>(5,40); //class Compare<int> * ptr = new Compare<int>(5,40); cout<<ptr->max()<<endl; cout<<compare.max()<<endl; cout<<compare.min()<<endl; system("pause"); return 0; }