一、构造函数:
1. 一个对象的初始化:在建立一个对象的时候,常常需要一些初始化的操作:赋初值,初始化等;
注:
不能在声明类类型的同时进行初始化:
class Time // 错误!!
{
hour = 0;
};
而且 仅仅在数据为public的情况下可以在定义对象的时候赋初值!!!:
class Time
{... } time1 = { 13,45,23 };
两个值之间通过逗号分隔开;
2. 构造函数的说明:
1. 构造函数是一种特殊的函数,用户不需要亲自去调用它,而是 在建立对象的同时自动执行!
2. 构造函数的名字必须和class 的名字相同,而不能由用户自己定义函数名;
3. 构造函数 不具有任何返回值,不具有任何类型;
4. 用户需要 自己定义函数体和函数参数变量表;
例: 类内定义构造函数:
class Time
{
private:
int hour;
int minute;
int sec;
public:
void SetTime();
void ShowTime();
Time()
{
hour = 0;
minute = 0;
sec = 0;
}
};
void Time :: SetTime()
{
cout << "Please Input the Time :" << endl;
cin >> hour >> minute >> sec;
}
void Time :: ShowTime()
{
cout << hour << ':' << minute << ':' << sec << endl;
}
int main()
{
Time time1;
time1.ShowTime();
time1. SetTime();
time1.ShowTime();
return 0;
}
在此例中,构造了一个构造函数,在建立对象的同时,自动执行这个函数,为各个变量赋初值;
例: 类外定义构造函数:
class Time
{
private:
int hour;
int minute;
int sec;
public:
void SetTime();
void ShowTime();
Time();
};
Time :: Time()
{
hour = 0;
minute = 0;
sec = 0;
}
void Time :: SetTime()
{
cout << "Please Input the Time :" << endl;
cin >> hour >> minute >> sec;
}
void Time :: ShowTime()
{
cout << hour << ':' << minute << ':' << sec << endl;
}
int main()
{
Time time1;
time1.ShowTime();
time1. SetTime();
time1.ShowTime();
return 0;
}
构造函数使用的几点说明:
1. 是在类对象进入作用域时,开始调用构造函数,即 对象开始被使用时;
2. 构造函数 没有返回值,所以在声明前加 void 也是错误的;
3. 构造函数 不能被用户自己调用,必须是在成员生命周期开始时 自己调用,且仅执行一次;
4. 如果用户 自己没有定义构造函数,则系统会自动生成一个构造函数,但函数体为空;
带参数的构造函数:
注:
真正的实参是在定义对象的时候给出的,此时 定义对象的格式:
class 类名 对象名(实参1,……);
例:
class Box
{
public:
Box(int ,int ,int );
int volume();
private:
int length;
int width;
int height;
};
Box :: Box(int l,int w,int h)
{
length = l;
width = w;
height = h;
}
int Box :: volume()
{
return (length * width * height);
}
int main()
{
Box box1(12,25,20);
cout << "The volume of this box is :" << box1.volume() << endl;
Box box2(15,30,21);
cout << "The volume of this box is :" << box2.volume() << endl;
return 0;
}
说明:
1. 带参构造函数中的形参,其对应实参在定义对象的时候给定;
2. 此时可以对不同的对象进行不同的初始化;
使用参数初始化表对数据成员初始化:
例:
Box :: Box(int l,int w,int h) : length(l),width(w),height(h){}// 后无分号;
即 在原来的函数首部加上冒号,然后列出初始化表;
构造函数的重载:
例:
class Box
{
public:
Box();
Box(int l,int w,int h): length(l),width(w),height(h) {}
int volume();
private:
int length;
int width;
int height;
};
Box :: Box()
{
length = 10;
width = 10;
height = 10;
}
int Box :: volume()
{
return (length * width * height);
}
int main()
{
Box box1;
cout << "The volume of this box is :" << box1.volume() << endl;
Box box2(15,30,21);
cout << "The volume of this box is :" << box2.volume() << endl;
return 0;
}
在此类中定义了两个构造函数:
第一个构造函数无参数: Box :: Box() { ... };
是在函数体中 对私有变量进行赋值的;
第二个构造函数是 直接在类体中定义的:
Box(int l,int w,int h): length(l),width(w),height(h) {}
有三个参数,是用参数初始化表对成员进行初始化;
说明:
1. 在调用时不必给出实参的构造函数,称为默认构造函数;
2. 当使用无参构造函数进行对象定义的时候,对象书写的语句应当正确:
Box box1;
而:
Box box1(); 是错误的,此时为声明一个普通函数其返回值为 Box型;
3. 在一个类中可以包含多个构造函数,但是系统只会执行一条;
使用默认参数的构造函数:
同之前的函数相同,构造函数也可以定义为含有默认参数的:
例:class Box
{
public:
Box(int l = 10,int w = 10,int h = 10); // 使用默认参数;
int volume();
private:
int length;
int width;
int height;
};
Box :: Box(int l,int w,int h): length(l),width(w),height(h) {};
int Box :: volume()
{
return (length * width * height);
}
int main()
{
Box box1;
cout << "The volume of this box is :" << box1.volume() << endl;
Box box2(15);
cout << "The volume of this box is :" << box2.volume() << endl;
Box box3(15,30);
cout << "The volume of this box is :" << box3.volume() << endl;
Box box4(15,30,20);
cout << "The volume of this box is :" << box4.volume() << endl;
return 0;
}
注:
实参和默认变量的结合是从左向右,所以应当把 设为默认参数的形参放在函数声明的末尾;
析构函数:
析构函数也是一个特殊的成员函数,作用与构造函数相反,函数名为类名前加 ~ ;
当对象的生命期结束的时候,系统将自动执行析构函数:
情况有:
1. 在某个函数中定义了一个对象(自动全局变量),当这个函数被调用时,对象应当释放,在释放前执行;
2. static 型变量在函数结束时也不释放,而是在main函数 或执行exit函数结束时,才调用函数;
3. 对于一个全局变量,在函数的流程超过其作用域的时候(main exit 结束时),调用析构函数;
4. 用 new 动态分配建立一个对象后,当用delete删除时,首先调用析构函数;
注:
1. 析构函数的作用不是释放空间,而是在释放空间前,执行一些清理工作;
2. 析构函数不返还任何值,没有函数类型,也没有函数参量,所以也不能被重载;
所以一个类可以有多个构造函数,但是只能有一个析构函数;
3. 析构函数其实是执行:用户希望在最后一次使用对象之后所执行的任何操作(输出信息等);
4. 如果用户没有定义析构函数,同样的,系统也会自动生成一个空函数体的析构函数;
class Student
{
public:
Student(int ,string ,char ); // 构造函数
~Student(); // 析构函数
void Display();
private:
int num;
string name;
char sex;
};
Student :: Student(int xuehao,string ming,char xingbie)
{
num = xuehao;
name = ming;
sex = xingbie;
cout << "The constructor is called !" << endl;
}
Student :: ~Student()
{
cout << "The Disconstructor is called !" << num <<endl;
}
void Student :: Display()
{
cout << "The number is :" << num << endl;
cout << "The name is :" << name << endl;
cout << "The sex is :" << sex << endl;
}
int main()
{
Student stu1(100,"ZK",'M');
stu1.Display();
Student stu2(101,"ZKK",'M');
stu2.Display();
return 0;
}
注: 调用
构造函数和析构函数的顺序:
先构造的后析构,后构造的先析构!!!!!!!!( 在同一个函数中定义的局部变量, 作用域相同 )
1. 在 全局中定义的对象(所有函数之外),他的构造函数在 所有函数执行之前被调用;
注:对于一个程序中 多个文件中都定义了全局变量,此时 构造函数的调用是不确定的;
当main 或 exit 执行后,调用析构函数;
2. 如果定义的是 局部变量(在某函数中),则在每一次 建立对象时调用其构造函数。
如果函数被多次调用,则 每一次建立对象时都需要调用构造函数;
当函数 调用结束、对象释放时,先调用析构函数;
3. 如果在函数中定义 静态static局部对象,则只在 程序第一次调用此函数时调用构造函数;
在 函数调用结束时,对象并不释放,也不调用析构函数;
当main 或 exit 执行后,调用析构函数;
例:
void fn()
{
Student stu1;
static Student stu2;
}
在调用 fn 函数时,先调用 stu1的构造函数,在执行stu2的构造函数;
在 fn 调用结束时,stu1 需要释放,此时调用 stu1的析构函数;
而stu2 是静态局部变量,只有在 main 结束时才释放,此时调用 stu2 的析构函数;
对象数组:
2. 构造函数如果 只有一个参数:
例:
Student :: Student(int num);
此时 可以直接在等号后面提供实参:
Student stud[3] = {60,61,62};
3. 构造函数 有多个参数:
例:
Student :: Student(int ,int ,int );
此时, 3个参数分别为三个元素的第一个实参,即:
编译系统只会为每一个对象元素的构造函数提供一个实参!!!
此时可以定义:
Student stud[3] = {
Student(1001,18,88);
Student(1002,11,22);
Student(1003,13,33);
};
即在建立对象数组时,分别调用构造函数,对每个元素初始化;
例:
class Box
{
public:
Box(int l,int w,int h) : length(l),width(w),height(h){}
int volume();
private:
int length;
int width;
int height;
};
int Box :: volume()
{
return (length * width * height);
}
int main()
{
Box box[3] = {
Box(10,10,10),
Box(10,10,15),
Box(10,15,10),
};
for(int i = 0; i < 3 ; i++)
{
cout << "The volume of the box " << i << " is " << box[i].volume() << endl;
}
cout << endl;
return 0;
}
对象指针:
一、指向对象的指针:
在建立对象时,编译系统会为每一个变量分配空间,而对象空间的起始地址就是变量名;
例:
Time *pt;
Time t1;
pt = &t1; // 将 Time 类型的变量 t1 的起始地址作为指针给 pt;
此时可以通过指针访问变量(public)
(*pt).hour
pt->hour
(*pt).get_time();
pt->get_time();
二、指向对象成员的指针:
1. 指向对象数据成员的指针:
此时和一般的数据指针的定义相同:
int *pt = &t1.hour;
cout << *pt << endl;
2. 指向对象成员函数的指针:
指向普通函数的指针变量的方法:
数据类型名 (* 指针变量名) (参数表列);
例:
void (* p)(); // p 是指向void型函数的指针变量;
此时可以使他指向一个函数:
p = fun;
(*p)() ; // 调用 fun 函数;
指向对象成员函数的指针:
注: p = t1.get_time; // 错误,编译系统要求赋值语句中左右两侧的类型相匹配;
而此时 p 为普通指针与 Time 类型无关,但是 get_time() 为 Time 型;
所以要求在以下三个方面都匹配:
1. 函数参数的类型和参数个数;
2. 函数返回值的类型;
3. 所属的类;
应当区别普通函数和成员函数的不同性质:
void (Time :: *p2)(); // 定义一个指向 Time 类 返回值为 void 公用成员函数的指针;
p2 = &Time :: get_time;
注:
定义括号不可省略:
否则成为: void Time :: *(p2()); // 返回值为 void 型指针的函数;
定义的一般形式为:
数据类型名 (类名 :: * 指针变量名) ( 参数表列 )
使指针变量指向一个公用成员函数的形式为:
指针变量名 = & 类名 :: 成员函数名;
class Time
{
public:
Time(int ,int ,int );
int hour;
int minute;
int sec;
void ShowTime();
};
Time :: Time(int h,int m,int s)
{
hour = h;
minute = m;
sec = s;
}
void Time :: ShowTime()
{
cout << "The time is : " << hour << " : " << minute << " : " << sec <<endl;
}
int main()
{
Time t1(12,14,30);
int *p1 = &t1.hour;
cout << "The hour is : " << *p1 << endl; // 定义一个指向 int 成员的普通指针;
t1.ShowTime(); // 成员调用函数;
Time *p2 = &t1;
p2->ShowTime(); // 定义一个指向对象的指针,并用指针指向成员函数
// void (Time :: *p3) () = &ShowTime;
void (Time :: *p3) (); // 定义一个指向成员函数的指针;
p3 = &Time :: ShowTime;
(t1.*p3) (); // 用成员符号调用函数指针;
return 0;
}
注: 成员函数是存放在对象空间外的空间中的,多个同类对象共用一个代码段,
所以赋给指针的应当是公用函数代码段的入口地址;
this 指针:
对于 n 个不同的对象,系统应当分配 n 个不同的空间,但是,他们 共有相同的函数段;
那么,当不同对象的成员函数调用函数是,是怎么 保证引用的是所指定的成员数据的呢?
其实在 每一个成员函数中包含着一个特殊的指针 this :
他是 指向本类的指针,它的值就是 当前被调用的成员函数所对应的对象的起始地址;
所以,当系统调用如:a.volume 函数时:
编译系统就会把 a 的起始地址给 this ,此时this 找到相应的成员函数;
实际上是执行 : (this -> length)*(this -> width)*(this -> height);
当调用 b.volume 时,编译系统又将 b 的起始地址给 this 完成操作;
注:
this 指针是作为隐式来使用的,它是作为参数被传递给成员函数的:
对于函数:
int Box :: volume()
{
return (length)*(width)*(height);
}
在 c++ 中被处理为:
int Box :: volume(Box *this)
{
return (this -> length)*(this -> width)*(this -> height);
}
而在 调用时,实际上是:
a.volume(&a); 从而将 a 的地址传给 this ;
在使用时, 也可以使用显式的this ,此时指代的是当前对象;