一、构造函数
构造函数的作用
构造函数是类中的特殊成员函数,它属于类的一部分。
给出类定义时,由程序员编写构造函数。
构造函数的作用时完成对象的初始化工作,在对象生成时,系统自动调用构造函数,用户在程序中不会直接调用构造函数。
构造函数的定义
定义一个类时,需要为类定义相应的构造函数。构造函数的函数名与类名相同,没有返回值。
一个类的构造函数可以有多个,即构造函数允许重载。
类名(形参1,形参2,...,形参n);
在声明类的构造函数时可以同时给出函数体,这样的构造函数称为内联函数。
假设类的成员变量是x1,x2,...,xn,则在类体外定义构造函数时有如下形式
形式一:
类名::类名(形参1,形参2,...,形参n):x1(形参1),x2(形参2),...,xn(形参n){}
形式二:
类名::类名(形参1,形参2,...,形参n)
{
x1 = 形参1;
x2 = 形参2;
...
xn = 形参n;
}
形式三:
类名::类名()
{
x1 = 初始化表达式1;
x2 = 初始化表达式2;
...
xn = 初始化表达式n;
}
构造函数参数的排列顺序可以是任意的,只要保证能与类的成员变量相对应即可。
还可以使用默认参数为成员变量赋初值,构造函数中实参的个数可以少于类的成员变量的个数。
例如,程序2-1中为类myDate定义了两个构造函数,
myDate::myDate()
{
year = 1997,month = 2,day = 12;
}
myDate::myDate(int y,int m,int d)
{
year = y;month = m;day = d;
}
改写,使用初始化列表的形式为类myDate的对象进行初始化。例3-1
myDate::myDate():year(1997),month(02),day(12){}
或是:
myDate::myDate(int y,int m,int d):year(y),month(m),day(d){}
构造函数也可以在类体外定义。假定类myDate中已经声明了下列4个构造函数:
myDate(); //不带参数
myDate(int); //带1个参数
myDate(int,int); //带2个参数
mydate(int,int,int); //带3个参数
则类体外,构造函数的定义如例3-2
myDate::myDate():year(1997),month(02),day(12){}
myDate::myDate(int d):year(1997),month(02)
{
day = d;
}
myDate::myDate(int m,int d):year(1997)
{
month = m;
day = d;
}
myDate::myDate(int y,int m,int d)
{
year = y;
month = m;
day = d;
}
例3-3 错误的构造函数定义
myDate::myDate(int d):year(1997),month(02)
{
day = d;
}
myDate::myDate(int d):year(1997),day(1)
{
month = d;
}
两个函数的参数列表是相同的。
构造函数的使用
例3-6 使用构造函数创建类的对象
假设已有例3-2中定义的构造函数,则创建对象时,可以使用下列形式:
myDate d();
myDate d1(25);
myDate d2(10,25);
myDate d3(1997,12,12);
例3-7 使用构造函数的默认参数创建对象
假设myDate类中仅定义了下列构造函数:
myDate::myDate(int y = 1997,int m = 2,int d = 12)
{
year = y;
month = m;
day = d;
}
则创建对象时,可以使用
myDate d0();
myDate d1(1980);
myDate d2(1990,3);
myDate d3(2000,3,12);
输出这4个对象的值,
1997/2/12
1980/2/12
1990/3/12
2000/3/12
例3-9 使用构造函数创建对象指针
假设已有例3-2中定义的构造函数,则创建对象时,可以使用下列形式:
myDate *pd = new myDate(); pd->printDate();
myDate *pd1 = new myDate(55); pd1->printDate();
myDate *pd2 = new myDate(66,77);pd2->printDate();
myDate *pd3 = new myDate(12,30,50);pd3->printDate();
使用new创建对象时,还可以写成:
myDate *pd = new myDate; //未加括号
程序3-1 对象数组使用默认构造函数进行初始化
修改程序2-2中定义的类Student,为类添加两个构造函数
class Student
{
public:
void setStudent(string,myDate); //设置学生信息
void setName(string); //设置学生姓名
string getName(); //获取姓名
void setBirthday(myDate); //设置生日
myDate getBirthday(); //获取生日
void printStudent() const; //打印信息
private:
string name; //姓名
myDate birthday; //生日
};
//在类体外定义成员函数
void Student::setStudent(string s,myDate d)
{
name = s;
birthday.setDate(d);
return;
}
void Student::setName(string n)
{
name = n;
return;
}
string Student::getName()
{
return name;
}
void Student::setBirthday(myDate d)
{
birthday.setDate(d);
return;
}
myDate Student::getBirthday()
{
return birthday;
}
void Student::printStudent() const
{
cout<<"姓名:"<<name<<"\t生日:";
birthday.printDate(); //调用类myDate的成员函数
cout<<endl;
}
在主函数main()中,可以使用无参数的构造函数生存Student对象stud,还可以声明Student对象的数组ss,ss[0]和ss[1]也是使用无参构造函数生成的。
int main() {
Student stud;
Student ss[2]; //调用两次默认构造函数
int y,m,d,i;
string name_;
stud.printStudent();
for(i = 0;i<2;i++)
ss[i].printStudent(); //输出默认值
for(i = 0;i<2;i++)
{
cout<<"请输入学的姓名和生日,生日以\”年 月 日\"的次序输入:";
cin>>name_>>y>>m>>d;
ss[i].setStudent(name_,myDate(y,m,d));
}
for(i = 0;i<2;i++)
ss[i].printStudent();
return 0;
}
数组中不仅能保存对象,还可以保存指向对象的指针,从而构成对象指针数组,
例3-11 使用构造函数生成对象指针数组
int main() {
Student stud;
stud.printStudent(); //输出默认值
stud.setName("111"); //姓名改为“111”
stud.printStudent();
Student *spointer[2] = {new Student(),&stud};
Student sy[2] = {Student(),stud};
for(int i = 0;i<2;i++)
spointer[i]->printStudent();
for(int i = 0;i<2;i++)
sy[i].printStudent();
return 0;
}
例3-12
int main() {
Student stud;
stud.printStudent(); //输出默认值
stud.setName("111"); //姓名改为“111”
stud.printStudent();
Student *spointer[2] = {new Student(),&stud};
Student sy[2] = {Student(),stud};
for(int i = 0;i<2;i++)
spointer[i]->printStudent();
for(int i = 0;i<2;i++)
sy[i].printStudent();
stud.setName("222");
for(int i = 0;i<2;i++)
spointer[i]->printStudent();
spointer[0]->setName("333");
spointer[1]->setName("444");
for(int i = 0;i<2;i++)
spointer[i]->printStudent();
stud.printStudent();
for(int i = 0;i<2;i++)
sy[i].printStudent();
return 0;
}
姓名:Noname 生日:1997/2/12
姓名:111 生日:1997/2/12
姓名:Noname 生日:1997/2/12
姓名:111 生日:1997/2/12
姓名:Noname 生日:1997/2/12
姓名:111 生日:1997/2/12
姓名:Noname 生日:1997/2/12
姓名:222 生日:1997/2/12
姓名:333 生日:1997/2/12
姓名:444 生日:1997/2/12
姓名:444 生日:1997/2/12
姓名:Noname 生日:1997/2/12
姓名:111 生日:1997/2/12
复制构造函数与类型转换构造函数
复制构造函数
复制构造函数是构造函数的一种,也称为拷贝构造函数。他的作用是使用一个已存在的对象去初始化另一个正在创建的对象。
复制构造函数只有一个参数,参数类型是本类的引用。
对于类A而言,复制构造函数的原型如下
A::A(const A &)
或
A::A(A &)
声明和实现复制构造函数的格式
class 类名
{
public:
类名(形参表); //构造函数
类名(类名 & 对象名); //复制构造函数
...
};
类名::类名(类名 & 对象名) //复制构造函数的实现
{
函数体
}
例3-13 调用复制构造函数
设有程序2-1和程序2-2中定义的类myDate和Student,则主函数中可以有:
Student stud;
Student ss[2] = {stud,Student()};或
Student ss[2];
ss[0]= Student(stud);
ss[1]= Student();
例3-14 自定义复制构造函数
在类Student中声明复制构造函数的原型
Student(const Student &s);
复制构造函数的函数体如下
Student::Student(const Student &s)
{
name = s.name;
birthday = s.birthday;
}
则在主函数中调用自定义的反正构造函数的语句如下:
Student ss[2] = {stud,Student()};
stud.printStudent();
为了验证效果,将复制构造函数修改如下:
Student::Student(const Student &s)
{
name = "COPY" + s.name;
birthday = s.birthday;
}
主函数中添加:
Student ss[2] = {stud,Student()};
stud.printStudent();
stud.setName("111");
ss[0] = Student(stud);
ss[1] = Student();
stud.printStudent();
ss[0].printStudent();
ss[1].printStudent();
*类型转换构造函数
如果构造函数只有一个参数,则可以看作是类型转换构造函数,他的作用是进行类型的自动转换。
程序3-2 类型转换构造函数的定义及使用
#include <iostream>
using namespace std;
class Demo
{
int id;
public:
Demo(int i) //构造函数
{
id = i;
cout<<"id="<<id<<"构造函数"<<endl;
}
void printDemo();
~Demo()
{
cout<<"id-"<<id<<"析构函数"<<endl;
}
};
void Demo::printDemo()
{
cout<<"id="<<id<<endl;
}
int main()
{
Demo d4(4);
d4.printDemo();
d4 = 6;
d4.printDemo();
return 0;
}
id=4构造函数
id=4
id=6构造函数
id-6析构函数
id=6
id-6析构函数
二、析构函数
析构函数也是成员函数的一种,它的名字也与类名相同。但要在类名前加一个“~”字符,以区别于构造函数。析构函数没有参数,也没有返回值。
一个类中有且仅有一个析构函数。不会有重载的析构函数。
例3-17 自定义析构函数
myDate::~myDate()
{
cout<<"myDate析构函数"<<endl;
}
Student::~Student()
{
cout<<"Student析构函数"<<endl;
}
当使用new运算符生成对象指针时,自动调用本类的构造函数。
例3-18 使用delete语句
修改类myDate和类Student的无参构造函数和析构函数
myDate::myDate():year(1997),month(2),day(12)
{
cout<<"myDate构造函数"<<endl;
myDate::~myDate()
{
cout<<"myDate析构函数"<<endl;
Student::Student():name("Noname"),birthday(myDate())
{
cout<<"Student构造函数"<<endl;
}
Student::~Student()
{
cout<<"Student析构函数"<<endl;
}
在主函数中执行
Student *stud = new Student();
delete stud;
myDate构造函数
Student构造函数
Student析构函数
myDate析构函数
例3-19 对象数组与delete语句
Student *ss = new Student[2];
delete []ss;
myDate构造函数
Student构造函数
myDate构造函数
Student构造函数
Student析构函数
myDate析构函数
Student析构函数
myDate析构函数
例3-20 对象指针数组与delete语句
Student *ss[2] = {new Student(),new Student()};
delete ss[0];
delete ss[1];
myDate构造函数
Student构造函数
myDate构造函数
Student构造函数
Student析构函数
myDate析构函数
Student析构函数
myDate析构函数
析构函数的调用执行顺序与构造函数相反。
例3-21
#include <iostream>
using namespace std;
class Samp
{
public:
void Setij(int a,int b)
{
i = a;
j = b;
}
~Samp()
{
cout<<"析构函数"<<i<<endl;
}
int GetMuti()
{
return i*j;
}
protected:
int i;
int j;
};
int main()
{
Samp *p;
p = new Samp[5];
if(!p)
{
cout<<"内存分配错误\n";
return 1;
}
for(int j = 0;j<5;j++)
p[j].Setij(j,j);
for(int k = 0;k<5;k++)
cout<<"Muti["<<k<<"]值是:"<<p[k].GetMuti()<<endl;
delete []p;
return 0;
}
Muti[0]值是:0
Muti[1]值是:1
Muti[2]值是:4
Muti[3]值是:9
Muti[4]值是:16
析构函数4
析构函数3
析构函数2
析构函数1
析构函数0
三、类的静态成员
静态变量
在C++中,可以使用static说明自动变量。根据定义的位置不同,分为静态全局变量和静态局部变量。
全局变量是指在所有花括号之外声明的变量,其作用域范围是全局可见的,即在整个项目文件内都有效。
使用static修饰的全局变量是静态全局变量,其作用域有所限制,仅在定义该变量的源文件内有效,项目中的其他源文件中不能使用它。
块内定义的变量是局部变量,从定义之处开始到本块结束处为止是局部变量的作用域。
使用static修饰的局部变量是静态局部变量。
静态局部变量具有局部作用域,但却具有全局生存期。
静态变量均存储在全局数据区,静态局部变量只执行一次初始化。
如果程序未显式给出初始值,则相当于初始化为0;如果显式给出初始值,则在该今天变量所在块的一次执行时完成初始化。
程序3-3 自动变量和静态变量的定义和使用
#include <iostream>
using namespace std;
static int glos = 100;
void f()
{
int a = 1; //局部自动变量a
static int fs = 1; //静态局部变量fs,完成初始化
cout<<"在f中:a(自动)="<<a<<" fs(静态)="<<fs<<" glos(静态)="<<glos<<endl;
a += 2;
fs += 2;
glos += 10;
cout<<"在f中:a(自动)="<<a<<" fs(静态)="<<fs<<" glos(静态)="<<glos<<"\n"<<endl;
// cout<<"ms="<<ms<<endl;
}
int main()
{
static int ms = 10;
for(int i = 1;i<= 3;i++) f();
// cout<<"fs="<<fs<<endl; //错误,变量fs不可见
cout<<"ms="<<ms<<endl;
cout<<"glos="<<glos<<endl;
return 0;
}
在f中:a(自动)=1 fs(静态)=1 glos(静态)=100
在f中:a(自动)=3 fs(静态)=3 glos(静态)=110
在f中:a(自动)=1 fs(静态)=3 glos(静态)=110
在f中:a(自动)=3 fs(静态)=5 glos(静态)=120
在f中:a(自动)=1 fs(静态)=5 glos(静态)=120
在f中:a(自动)=3 fs(静态)=7 glos(静态)=130
ms=10
glos=130
类的静态成员
类的静态成员有两种:静态成员变量和静态成员函数。类的静态成员被类的所有对象共享,不论有多少对象存在,静态成员都只有一份保存在公用内存中。对于静态成员变量,各对象看到的值是一样的。
给静态成员变量赋初值的格式
类型 类名::静态成员变量=初值;
访问类静态成员的一般格式
类名::静态成员名
或
对象名.静态成员名
或
对象指针->静态成员名
类的静态成员函数没有this指针,不能在静态成员函数内访问非静态的成员。
程序3-4 静态成员的使用
#include <iostream>
using namespace std;
class classA
{
public:
double x,y;
static int num; //公有静态成员变量——供所有对象“共享”,用于记录通过构造函数已生成的对象个数
classA()
{
x = 0;
y = 0;
num++; //每生成一个对象,num加1
}
classA(double x0,double y0)
{
x = x0;
y = y0;
num++;
}
static void staticFun() //静态成员函数,输出静态成员变量num的当前值
{
cout<<"current_num="<<num<<endl;
// cout<<"x="<<x<<endl; //错误,在静态函数中不能访问非静态变量
}
};
int classA::num = 0; //必须在类体外(使用类名限定)初始化静态成员变量
int main()
{
classA obj(1.2,3.4),*p; //调用1次构造函数
cout<<"classA::num="<<classA::num<<endl; //使用类名做限定符
classA::staticFun();
cout<<"obj.num="<<obj.num<<endl; //使用对象名做限定符
obj.staticFun();
cout<<endl;
classA A[3]; //调用3次构造函数
cout<<"classA::num="<<classA::num<<endl;
classA::staticFun();
cout<<endl;
p = new classA(5.6,7.8);
cout<<"classA::num="<<classA::num<<endl;
classA::staticFun();
cout<<"p->num="<<p->num<<endl;
p->staticFun();
return 0;
}
classA::num=1
current_num=1
obj.num=1
current_num=1
classA::num=4
current_num=4
classA::num=5
current_num=5
p->num=5
current_num=5
静态成员变量本质上是全局变量。对于一个类,哪怕它的一个对象都不存在,其静态成员变量也存在。静态成员函数并不需要作用在某个具体的对象上,因此本质上是全局函数。
四、变量即对象的生存期和作用域
变量的生存期和作用域
变量的生存期是指变量所占据的内存空间由分配到释放的时期。
变量有效的范围称为其作用域。
作用域分为全局域和局部域。
全局域包括程序作用域和文件作用域;局部域包括类作用域、函数作用域、块作用域和函数原型作用域等。
程序作用域也称为多文件作用域,属于程序作用域的由通过extern关键字进行说明的外部变量以及外部函数等。
重名标识符指的是在程序中被重复定义的同名标识符。在相同的作用域内,标识符不能被重复定义。重名标识符的作用域遵循如下规则:
- 没有包含关系的两个不同作用域,在其中说明的标识符尽管名字相同,但二者毫不相干。
- 具有包含关系的两个不同作用域,将它们看成是互不相同的名字;进入子范围后,将屏蔽其父范围的名字。
程序3-5 变量生存期与作用域
#include <iostream>
using namespace std;
int main()
{
int i = 10; //整型变量i,具有函数作用域
char ch = '1'; //字符型变量ch,具有函数作用域
cout<<"在main中 --i=" <<i<<",\tch="<<ch<<endl;
{
int i = 20; //另一个整型变量,外层块作用域
char ch = '2';
cout<<"在外层块 --i="<<i<<",\tch="<<ch<<endl;
if(i>0)
{
double i = 30.3;
int ch = 33;
cout<<"在内层块 --i="<<i<<",\tch="<<ch<<endl;
}
cout<<"在外层块 --i="<<i<<",\tch="<<ch<<endl;
}
cout<<"在main中 --i=" <<i<<",\tch="<<ch<<endl;
return 0;
}
在main中 --i=10, ch=1
在外层块 --i=20, ch=2
在内层块 --i=30.3, ch=33
在外层块 --i=20, ch=2
在main中 --i=10, ch=1
程序3-6
#include <iostream>
using namespace std;
int x = 11;
char ch = '1';
void fun1(int ipara1);
void fun2()
{
int i = 22222;
double ch = 202.2;
cout<<"fun2中 --x="<<x<<",\tch="<<ch<<",\ti="<<i<<endl;
}
int main()
{
cout<<"在main中 --x="<<x<<",\tch="<<ch<<endl;
fun1(x);
fun2();
return 0;
}
void fun1(int ii)
{
int i = 2111;
int x = 201;
cout<<"在fun1中 --ii="<<ii<<",\tx="<<x<<",\tch="<<ch<<",\ti="<<i<<endl;
}
在main中 --x=11, ch=1
在fun1中 --ii=11, x=201, ch=1, i=2111
fun2中 --x=11, ch=202.2, i=22222
类对象的生存期和作用域
类的对象在生成时调用构造函数,在消亡时调用析构函数,在这两个函数调用之间即是对象的生存期。
程序3-7 对象的生存期与作用域
#include <iostream>
#include <string>
using namespace std;
class Demo
{
int id;
string position;
public:
Demo(int i,string pos) //构造函数
{
id = i;
position = pos;
cout<<"生成对象"<<this<<",id="<<id<<" 位置:"<<position<<endl;
}
Demo(int i):position("temp") //类型转换构造函数
{
id = i;
cout<<"生成对象"<<this<<",id="<<id<<" 位置:"<<position<<endl;
}
~Demo()
{
cout<<"消亡对象"<<this<<",id="<<id<<" 位置:"<<position<<endl;
}
};
Demo d1(1,"全局");
void Func()
{
cout<<"enter func"<<endl;
static Demo d2(2,"函数Func内,静态的");
Demo d3(3,"函数Func内");
cout<<"exit func"<<endl;
}
int main()
{
cout<<"enter main"<<endl;
Demo d4(4,"主函数内");
d4 = 7;
cout<<"enter block"<<endl;
{
Demo d5(5,"主函数块内");
}
cout<<"exit block"<<endl;
static Demo d6(6,"主函数内,静态的");
Func();
cout<<"main ends"<<endl;
return 0;
}
生成对象0x4a9030,id=1 位置:全局
enter main
生成对象0x70fdc0,id=4 位置:主函数内
生成对象0x70fde0,id=7 位置:temp
消亡对象0x70fde0,id=7 位置:temp
enter block
生成对象0x70fdb0,id=5 位置:主函数块内
消亡对象0x70fdb0,id=5 位置:主函数块内
exit block
生成对象0x4a9070,id=6 位置:主函数内,静态的
enter func
生成对象0x4a9060,id=2 位置:函数Func内,静态的
生成对象0x70fd40,id=3 位置:函数Func内
exit func
消亡对象0x70fd40,id=3 位置:函数Func内
main ends
消亡对象0x70fdc0,id=7 位置:temp
消亡对象0x4a9060,id=2 位置:函数Func内,静态的
消亡对象0x4a9070,id=6 位置:主函数内,静态的
消亡对象0x4a9030,id=1 位置:全局
五、常量成员和常引用成员
由关键字const修饰的类成员变量称为类的常量成员变量。必须进行初始化,而且只能通过构造函数的成员初始化列表的方式进行。
使用const修饰的函数称为常量函数。
定义常量对象或常量成员变量的格式
const 数据类型 常量名 = 表达式;
定义常量函数的格式
类型说明符 函数名(参数表) const;
例3-26 使用常量对象不能调用非常量成员函数
class CDemo
{
public:
void SetValue(){}
};
如果主函数中有:
class CDemo
{
public:
void SetValue(){}
};
int main()
{
const CDemo Obj; //Obj是常量对象
Obj.SetValue(); //错误
}
程序3-8 调用常量成员函数与普通成员函数
#include <iostream>
#include <string>
using namespace std;
class Sample
{
public:
Sample();
void getValue() const; //常量成员函数
void getValue(); //非常量成员函数
void priValue(); //非常量成员函数
void priVcon() const; //常量成员函数
};
Sample::Sample(){}
void Sample::getValue()const //常量成员函数
{
cout<<"常量成员函数"<<endl;
}
void Sample::getValue()
{
cout<<"非常量成员函数"<<endl;
}
void Sample::priValue()
{
cout<<"非常量成员函数"<<endl;
}
void Sample::priVcon()const
{
cout<<"常量成员函数"<<endl;
}
int main()
{
const Sample cono;
Sample o;
cout<<"cono\t";
cono.getValue();
// cono.priValue(); //错误,不能调用非常量成员函数
cout<<"o\t";
o.getValue();
cout<<"o\t";
o.priValue();
cout<<"o\t";
o.priVcon();
return 0;
}
cono 常量成员函数
o 非常量成员函数
o 非常量成员函数
o 常量成员函数
程序3-9 常量成员变量及常量成员函数的使用
#include <iostream>
#include <string>
using namespace std;
class constClass
{
const int conMbr;
int Mbr;
public:
constClass():conMbr(0),Mbr(100){}
constClass(int i):conMbr(i)
{
Mbr = 200;
}
void printConst()
{
cout<<"conMbr="<<conMbr<<",Mbr="<<Mbr<<endl;
}
int getConst()
{
cout<<"调用非常量函数"<<endl;
return conMbr;
}
int getConst() const
{
cout<<"调用常量函数"<<endl;
return conMbr;
}
int getValue()
{
return Mbr;
}
void processConst()
{
cout<<"-- 在processConst函数中 非常量 --"<<endl;
int x = 2*conMbr+1;
cout<<"x=2*conMbr+1="<<x<<endl; //可以读取conMbr
// conMbr++;//错误,不能更改常量成员变量conMbr的值
Mbr++; //可以修改非常量Mbr的值
cout<<"Mbr="<<Mbr<<endl;
}
void processConst() const
{
cout<<"--在processConst函数中 常量--"<<endl;
int x = conMbr + 1;
cout<<"x = conMbr + 1="<<x<<endl;
// conMbr++;//错误
// Mbr++; //错误
cout<<"Mbr="<<Mbr<<endl;
}
};
int main()
{
constClass ob1(123),ob2;
ob1.printConst();
cout<<"ob2.getConst()="<<ob2.getConst()<<endl;
ob2.printConst();
const constClass ob3(20);
cout<<"ob3.getConst()="<<ob3.getConst()<<endl;
ob3.processConst();
return 0;
}
conMbr=123,Mbr=200
调用非常量函数
ob2.getConst()=0
conMbr=0,Mbr=100
调用常量函数
ob3.getConst()=20
--在processConst函数中 常量--
x = conMbr + 1=21
Mbr=200
程序3-10 常引用型成员变量
#include <iostream>
#include <string>
using namespace std;
int fvalue = 10;
class CDemo
{
public:
const int num;
const int& ref;
int value;
public:
CDemo(int n):num(n),ref(value),value(4){}
};
int main()
{
cout<<sizeof(CDemo)<<endl;
CDemo f(100);
// f.ref = f.value;
cout<<"f.num="<<f.num<<"\tf.ref="<<f.ref<<",\tf.value="<<f.value<<endl;
return 0;
}
24
f.num=100 f.ref=4, f.value=4
六、成员对象和封闭类
一个类的成员变量如果是另一个类的对象,则该成员变量称为成员对象。这两个类为包含关系。包含成员对象的类叫做封闭类。
封闭类构造函数的初始化列表
在封闭类构造函数中添加初始化列表的格式
封闭类名::构造函数名(参数表):成员变量1(参数表),成员变量2(参数表),...
{...}
程序3-11 封闭类的定义
#include <iostream>
#include <string>
using namespace std;
class CTyre //轮胎类
{
private:
int radius; //半径
int width; //宽度
public:
CTyre():radius(16),width(185){} //构造函数
CTyre(int r,int w):radius(r),width(w){} //构造函数
int getRadius()
{
return radius;
}
int getWidth()
{
return width;
}
};
class CCar //汽车类,封闭类
{
private:
int price; //价格
CTyre tyre; //成员对象
public:
CCar();
CCar(int p,int tr, int tw);
int getPrice()
{
return price;
}
CTyre getCTyre()
{
return tyre;
}
};
CCar::CCar()
{
price = 50010;
CTyre();
};
CCar::CCar(int p,int tr,int tw):price(p),tyre(tr,tw){}; //使用初始化列表
int main()
{
CCar car(48900,17,225);
cout<<"price="<<car.getPrice();
cout<<"\tCTyre.Radius="<<car.getCTyre().getRadius()<<"\tCTyre.Width="<<car.getCTyre().getWidth()<<endl;
CCar car1;
cout<<"price="<<car1.getPrice();
cout<<"\tCTyre.Radius="<<car1.getCTyre().getRadius()<<"\tCTyre.Width="<<car1.getCTyre().getWidth()<<endl;
}
price=48900 CTyre.Radius=17 CTyre.Width=225
price=50010 CTyre.Radius=16 CTyre.Width=185
程序12 封闭类对象的创建与消亡
#include <iostream>
#include <string>
using namespace std;
class CTyre //轮胎类
{
private:
int radius; //半径
int width; //宽度
public:
CTyre():radius(16),width(185)
{
cout<<radius<<"\tCTyre 构造函数"<<endl;
}
CTyre(int r,int w):radius(r),width(w)
{
cout<<radius<<"\tCTyre 构造函数"<<endl;
}
~CTyre()
{
cout<<radius<<"\tCTyre 析构函数"<<endl;
}
int getRadius()
{
return radius;
}
int getWidth()
{
return width;
}
};
class CCar //汽车类,封闭类
{
private:
int price; //价格
CTyre tyre; //成员对象
public:
CCar();
CCar(int p,int tr, int tw);
~CCar();
int getPrice()
{
return price;
}
CTyre getCTyre()
{
return tyre;
}
};
CCar::CCar()
{
price = 50010;CTyre();cout<<price<<"\tCCar构造函数"<<endl;
};
CCar::CCar(int p,int tr,int tw):price(p),tyre(tr,tw)
{
cout<<price<<"\t构造函数"<<endl;
}
CCar::~CCar()
{
cout<<price<<"\tCCar 析构函数"<<endl;
}
int main()
{
CCar car(48900,17,225);
cout<<"price="<<car.getPrice();
cout<<"\tCTyre.Radius="<<car.getCTyre().getRadius()<<"\tCTyre.Width="<<car.getCTyre().getWidth()<<endl;
CCar car1;
cout<<"price="<<car1.getPrice();
cout<<"\tCTyre.Radius="<<car1.getCTyre().getRadius()<<"\tCTyre.Width="<<car1.getCTyre().getWidth()<<endl;
}
17 CTyre 构造函数
48900 构造函数
price=48900 CTyre.Radius=17 CTyre.Width=225
17 CTyre 析构函数
17 CTyre 析构函数
16 CTyre 构造函数
16 CTyre 构造函数
16 CTyre 析构函数
50010 CCar构造函数
price=50010 CTyre.Radius=16 CTyre.Width=185
16 CTyre 析构函数
16 CTyre 析构函数
50010 CCar 析构函数
16 CTyre 析构函数
48900 CCar 析构函数
17 CTyre 析构函数
*封闭类的复制构造函数
如果封闭类的对象是用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化。
程序3-13 复制构造函数的使用
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A()
{
cout<<"default"<<endl;
}
A(A &a)
{
cout<<"copy"<<endl;
}
};
class B
{
A a;
};
int main()
{
B b1,b2(b1);
return 0;
}
default
copy
七、友元
友元
友元实际上并不是面向对象的特征,而是为了兼顾C语言程序设计的习惯与C++信息隐藏的特点,而特意增加的功能。
这是一种类成员的访问权限。
友元的概念破坏了类的封装性和信息隐藏,但有助于数据共享,能够提高程序执行的效率。
友元使用关键字friend标识。在类定义中,当friend出现在函数说明语句的前面时,表示该函数为类的友元函数。一个函数可以同时说明为多个类的友元函数,一个类中也可以有多个友元函数。
友元函数
在类定义中,将一个全局函数声明为本类友元函数的格式
friend 返回值类型 函数名(参数表);
当有某类A的定义后,将类A的成员函数说明为本类的友元函数的格式
friend 返回值类型 类A::类A的成员函数名(参数表);
不能把其他类的私有成员函数声明为友元函数。
友元函数不是类的成员函数,但允许访问类中的所有成员。
友元函数不受类中的发访问权限关键字限制,可以把它放在类的公有、私有、保护部分,结果一样。
程序3-14 友元函数的声明
#include <iostream>
#include <cmath>
using namespace std;
class Pixel; //前向引用声明
class Test
{
public:
void printX(Pixel p); //用到了类Pixel
};
class Pixel
{
private:
int x,y;
public:
Pixel(int x0,int y0)
{
x = x0;
y = y0;
}
void printxy()
{
cout<<"pixel:("<<x<<","<<y<<")"<<endl;
}
friend double getDist(Pixel p1,Pixel p2); //友元函数(原型),全局函数
friend void Test::printX(Pixel p); //类Test的成员函数为本类的友元函数
};
void Test::printX(Pixel p)
{
cout<<"x="<<p.x<<"\ty="<<p.y<<endl; //访问类Pixel的私有成员
return;
}
double getDist(Pixel p1,Pixel p2) //友元函数在类体外定义
{
double xd = double(p1.x-p2.x);
double yd = double(p1.y-p2.y);
return sqrt(xd*xd + yd*yd); //两点间距离
}
int main()
{
Pixel p1(0,0),p2(10,10);
p1.printxy();
p2.printxy();
cout<<"(p1,p2)间距离 = "<<getDist(p1,p2)<<endl; //直接调用全局函数
Test t;
cout<<"从友元函数中输出---"<<endl;
t.printX(p1);
t.printX(p2);
return 0;
}
pixel:(0,0)
pixel:(10,10)
(p1,p2)间距离 = 14.1421
从友元函数中输出---
x=0 y=0
x=10 y=10
程序3-15 类成员函数实现复数类操作
#include <iostream>
using namespace std;
class myComplex //复数类
{
private:
double real,imag; //复数的实部和虚部
public:
myComplex();
myComplex(double r,double i);
myComplex addCom(myComplex c); //成员函数,调用者对象与参数对象c相加
void outCom(); //成员函数,输出调用者对象的有关数据
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
myComplex myComplex::addCom(myComplex c)
{
return myComplex(real + c.real,imag + c.imag);
}
void myComplex::outCom()
{
cout<<"("<<real<<","<<imag<<")";
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
res = c1.addCom(c2); //调用友元函数必须通过类对象
c1.outCom();
cout<<"+";
c2.outCom();
cout<<"=";
res.outCom();
cout<<endl;
return 0;
}
(1,2)+(3,4)=(4,6)
程序3-16 友元函数实现复数类操作
#include <iostream>
using namespace std;
class myComplex //复数类
{
private:
double real,imag; //复数的实部和虚部
public:
myComplex();
myComplex(double r,double i);
friend myComplex addCom(myComplex c1,myComplex c2); //友元函数 两个参数对象c1与c2相加
friend void outCom(myComplex c); //友元函数 输出参数对象c的有关数据
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
myComplex addCom(myComplex c1,myComplex c2)
{
return myComplex(c1.real + c2.real,c1.imag + c2.imag);
}
void outCom(myComplex c)
{
cout<<"("<<c.real<<","<<c.imag<<")";
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
res = addCom(c1,c2); //调用友元函数必须通过类对象
outCom(c1);
cout<<"+";
outCom(c2);
cout<<"=";
outCom(res);
cout<<endl;
return 0;
}
(1,2)+(3,4)=(4,6)
友元类
在类定义中声明友元类的格式
friend class 类名;
友元类的关系是单向的。若说明类B是类A的友元类,不等于类A也是类B的友元类。
改写实现复数类的程序3-15和程序3-16,将操作定义为一个操作类oper,然后将类oper说明为Complex的友元类。
程序3-17 友元类示例
#include <iostream>
using namespace std;
class myComplex //复数类
{
private:
double real,imag; //复数的实部和虚部
public:
myComplex();
myComplex(double r,double i);
friend class oper; //友元类
};
myComplex::myComplex()
{
real = 0;
imag = 0;
}
myComplex::myComplex(double r,double i)
{
real = r;
imag = i;
}
class oper
{
public:
myComplex addCom(myComplex c1,myComplex c2); //成员函数,两个参数对象c1与c2相加
void outCom(myComplex c); //成员函数,输出参数对象c的有关数据
};
myComplex oper::addCom(myComplex c1,myComplex c2)
{
return myComplex(c1.real + c2.real,c1.imag + c2.imag);
}
void oper::outCom(myComplex c)
{
cout<<"("<<c.real<<","<<c.imag<<")";
}
int main()
{
myComplex c1(1,2),c2(3,4),res;
oper o;
res = o.addCom(c1,c2); //通过类oper的对象调用操作类的成员函数
o.outCom(c1);
cout<<"+";
o.outCom(c2);
cout<<"=";
o.outCom(res);
cout<<endl;
return 0;
}
(1,2)+(3,4)=(4,6)
例3-31 计算两点间的距离
#include <iostream>
#include <cmath>
using namespace std;
class Point //定义的是平面坐标系下的点
{
private:
double x,y;
friend class Line;
public:
Point(double i = 0,double j = 0)
{
x = i;
y = j;
}
Point(Point &p)
{
x = p.x;
y = p.y;
}
};
class Line //定义的是经过两点的直线
{
private:
Point p1,p2;
public:
Line(Point &xp1,Point &xp2):p1(xp1),p2(xp2){}
double GetLength(); //返回给定两点间的距离
};
double Line::GetLength()
{
double dx = p2.x - p1.x;
double dy = p2.y - p1.y;
return sqrt(dx*dx + dy*dy);
}
int main()
{
Point p1,p2(6,8);
Line L1(p1,p2);
cout<<L1.GetLength()<<endl;
}
八、this指针
C++语言规定,当调用一个成员函数时,系统自动向它传递一个隐含的参数。该参数是一个指向调用该函数的对象的指针,称为this指针。
this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数联系在一起,从外部看来,好像每一个对象都拥有自己的成员函数。
C++规定,在非静态成员函数内部可以直接使用this关键字,this就代表指向该函数所作用的对象的指针。
程序3-18 this指针的使用
#include <iostream>
using namespace std;
class myComplex
{
public:
double real,imag;
myComplex():real(0),imag(0){}
myComplex(double,double);
myComplex AddRealOne();
myComplex AddImagOne();
void outCom();
};
myComplex::myComplex(double real,double imag)
{
this->real = real;
this->imag = imag;
}
void myComplex::outCom()
{
cout<<"("<<real<<","<<imag<<")";
}
myComplex myComplex::AddRealOne()
{
this->real++;
return *this;
}
myComplex myComplex::AddImagOne()
{
this->imag++;
return *this;
}
int main()
{
myComplex c1(1,1),c2,c3;
c1.outCom();
c2.outCom();
c3.outCom();
cout<<endl<<"我是分界线"<<endl;
c2 = c1.AddRealOne();
c1.outCom();
c3 = c1.AddImagOne();
c2.outCom();
c3.outCom();
cout<<endl;
return 0;
}
(1,1)(0,0)(0,0)
我是分界线
(2,1)(2,1)(2,2)
网友评论