x12using namespace std;3int main()4{5 cout << "hello C++" << endl;6 return 0;7}8
例1-1所示的C++程序,文件扩展名是“.cpp”。
第1行代码作用是包含标准输入/输出头文件iostream。
第2行代码作用是引用标准命名空间std。
第5行代码在屏幕输出“hello C++”。
cout是预定义的输出流对象,用于输出数据;endl表示换行。
xxxxxxxxxx14123using namespace std;4int main()5{6 cout<<setw(10)<<3.1415<<endl;7 cout<<setw(10)<<setfill('0')<<3.1415<<endl;8 cout<<setw(10)<<setfill('0')9 <<setiosflags(ios::left)<<3.1415<<endl;10 cout<<setw(10)<<setfill('-')11 <<setiosflags(ios::right)<<3.1415<<endl;12 return 0;13}14
在例1-2中,第6行代码输出了指定域宽的数据,
第7行代码输出了填充了字符的数据,
第8~11行代码输出的是设置了左对齐和右对齐格式的数据。
xxxxxxxxxx1412using namespace std; 3void add(int x,int y=1,int z=2) 4{5 cout<<x+y+z<<endl;6}7int main()8{9 add(1); //只传递1给形参x,y、z使用默认形参值10 add(1,2); //传递1给x,2给y,z使用默认形参值11 add(1,2,3); //传递三个参数,不使用默认形参值12 return 0;13}14
在例1-3中,第3~6行代码定义了函数add(),该函数指定了参数y、z的默认值。
第9~11行代码在main()中调用了三次add()函数。
xxxxxxxxxx2112using namespace std;3void add(int x, int y)4{5 cout << "int: " << x + y << endl;6}7void add(double x)8{9 cout << "double: " << 10 + x << endl;10}11double add(double x, double y)12{13 return x + y;14}15int main()16{17 add(10.2); //一个double类型参数18 add(1, 3); //两个int类型参数19 return 0;20}21
在例1-4中,第3~14行代码定义了三个重载函数add()。
第17行代码调用add()函数,传入一个double类型的实参10.2。
第18行代码调用add()函数,传入两个int类型的实参1和3。
xxxxxxxxxx1212using namespace std;3int main()4{5 int a=10;6 int &ra=a;7 cout<<"变量a的地址"<<hex<<&a<<endl;8 cout<<"引用ra的地址:"<<hex<<&ra<<endl;9 cout<<"引用ra的值:"<<dec<<ra<<endl;10 return 0;11}12
在例1-5中,第5行代码定义了整型变量a并初始化为10。
第6行代码定义了指向变量a的引用ra。
第7~9行代码分别输出变量a的地址、引用ra的地址、引用ra的值。
xxxxxxxxxx1812using namespace std;3void exchange(int& x, int& y)4{5 int temp = x;6 x = y;7 y = temp;8}9int main()10{11 int a, b;12 cout << "please input two nums: " << endl;13 cin >> a >> b;14 exchange(a, b);15 cout << " exchange: " << a << " "<< b << endl;16 return 0;17}18
在例1-6中,第3~8行代码定义了一个函数exchange(),用于交换两个int类型变量的值。exchange()函数有两个int类型的引用作为参数。
第13~15行代码通过cin从键盘输入两个整型数据给变量a、b,调用exchange()函数交换变量a、b的值,并输出交换结果。
xxxxxxxxxx2012using namespace std;3int main()4{5 int *pi = new int(10); //new 一个int对象,初始值为106 cout<<"*pi="<<*pi<<endl;7 *pi = 20; //通过指针改变内存中的值8 cout<<"*pi = "<<*pi<<endl;9 //创建一个大小为10char类型的数组10 char *pc = new char[10];11 for (int i = 0;i < 10;i++)12 pc[i] = i + 65; //向数组中存入元素13 for (int i = 0;i < 10;i++)14 cout<<pc[i]<<" ";15 cout<<endl;16 delete pi; //释放int对象17 delete []pc; //释放char数组对象18 return 0;19}20
在例1-7中,第5行代码使用new创建了一个int对象,初始值为10。
第6行代码通过指针pi输出内存中的数据,输出结果为10。
第7~8行代码通过指针pi修改内存中的数据为20,并输出,输出结果为20。
第10~12行代码使用new创建一个大小为10的char类型数组,并通过for循环为数组赋值。
第13~14行代码通过for循环输出数组中的元素,
第16~17行代码使用delete运算符释放int对象和char类型数组对象。
xxxxxxxxxx4123char* func(int,char*);4
xxxxxxxxxx9123char* func(int size,char *str)4{5 char *p =malloc(size);6 strcpy(p,str);7 return p;8}9
例1-8和例1-9的mallocStr.h文件和mallocStr.c文件所示代码是C语言程序。其中,mallocStr.c文件中定义了func()函数,在函数内部调用malloc()函数申请一块内存空间存储一个字符串。func()函数第一个参数指定申请内存的大小,第二个参数是存入内存空间的字符串。
xxxxxxxxxx1912 using namespace std;34extern "C"5{67 89}1011int main()12{13 char str[]="C++";14 char *p=func(sizeof(str)+1,str);15 cout<<p<<endl;16 free (p);17 return 0;18}19
在例1-10所示的main.cpp中,程序调用了func()函数,则需要使用extern "C" 声明m allocStr.h文件内容以C语言的方式编译。
下面通过案例演示类的定义、对象的创建及对象的成员访问,如例2-1所示。
xxxxxxxxxx2912using namespace std;3class Student //定义学生类Student4{5public: //公有类型6 void study(); //声明表示学习的成员函数7 void exam(); //声明表示考试的成员函数 8 string _name; //声明表示姓名的成员变量9 int _age; //声明表示年龄的成员变量10};11void Student::study() //类外实现studey()成员函数12{13 cout << "学习C++" << endl;14}15void Student::exam() //类外实现exam()成员函数16{17 cout << "C++考试成绩100分" << endl;18}19int main()20{21 Student stu; //创建Student类对象22 stu._name = "张三"; //设置对象姓名23 stu._age = -20; //设置对象年龄24 cout << stu._name << stu._age << "岁" << endl;25 stu.study(); //调用study()成员函数26 stu.exam(); //调用exam()成员函数27 return 0;28}29
在 例2-1 中,第3~10行代码定义了学生类 Student ,该类中有两个公有成员变量 name 和 age ,分别表示学生的姓名和年龄,有两个公有成员函数study()和exam()。
第11~18行代码是在类外实现类的成员函数。
第21~23行代码,在 main() 函数中创建 Student 类对象 stu ,并设置对象 stu 的 name 和 age 值。
第24~26行代码通过对象stu调用对象的成员函数,输出对象stu的信息。
xxxxxxxxxx6312using namespace std;3class Student //定义学生类Student4{5public: //公有类型6 void study(); //声明表示学习的成员函数7 void exam(); //声明表示考试的成员函数8 void setName(string name); //声明设置姓名的成员函数9 void setAge(int age); //声明设置年龄的成员函数10 string getName(); //声明获取姓名的成员函数11 int getAge(); //声明获取年龄的成员函数12private: //私有类型13 string _name; //声明表示姓名的成员变量14 int _age; //声明表示年龄的成员变量15};16void Student::study() //类外实现studey()成员函数17{18 cout << "学习C++" << endl;19}20void Student::exam() //类外实现exam()成员函数21{22 cout << "C++考试成绩100分" << endl;23}24void Student::setName(string name) //类外实现setName()成员函数25{26 _name = name;27}28void Student::setAge(int age) //类外实现setAge()成员函数29{30 if (age < 0 || age > 100)31 {32 cout << "_name" << "年龄输入错误" << endl;33 _age = 0;34 }35 else36 _age = age;37}38string Student::getName() //类外实现getName()函数39{40 return _name;41}42int Student::getAge() //类外实现getAge()函数43{44 return _age;45}46int main()47{48 Student stu; //创建Student类对象stu49 stu.setName("张三"); //设置对象stu的姓名50 stu.setAge(-20); //设置对象stu的年龄51 //调用成员函数getName()和getAge()获取对象stu的姓名、年龄,并输出52 cout << stu.getName() << stu.getAge() << "岁" << endl;53 stu.study(); //调用成员函数study()54 stu.exam(); //调用成员函数exam()55 Student stu1; //创建Student类对象stu156 stu1.setName("李四");57 stu1.setAge(22);58 cout << stu1.getName() << stu1.getAge() << "岁" << endl;59 stu1.study();60 stu1.exam();61 return 0;62}63
例2-2是对例2-1的修改,将Student中的成员变量name和age定义为私有成员,并定义了公有成员函数setNam e()、setAge()、getNam e()和getAge(),分别用于设置和获取对象的姓名和年龄。
第28~37行代码,在实现setAge()时,对传入的参数age进行了判断处理,如果age>100或age<0,则输出“年龄输入错误”的信息,并将 _age 值设置为0。
第48~52行代码,创建对象stu,调用 setName() 函数和 setAge() 函数,分别用于设置对象stu的 _name和 _age;调用 getName() 函数和 getAge() 函数,分别用于获取对象stu的 _name和 _age。
第56~60行代码,创建Student类对象stu1,设置其姓名和年龄,并获取对象stu1的姓名和年龄将其输出。
当设置对象stu的年龄为-20时,程序提示年龄输入错误,并将表示年龄的 _age设置为0;当设置对象stu1的 _age为22时,程序正确输出对象stu1的年龄。
在例2-2中, _name 和 _age 成员为私有成员,因此不能通过对象直接访问,如果仍然像例2-1中的第22~24行代码一样,直接通过对象访问 _name 和 _age,编译器会报错。
xxxxxxxxxx33123using namespace std;4class Clock //定义时钟类Clock5{6public:7 Clock(); //声明无参构造函数8 void showTime(); //声明显示时间的成员函数9private:10 int _hour; //声明表示小时的成员变量11 int _min; //声明表示分钟的成员变量12 int _sec; //声明表示秒的成员变量13};14Clock::Clock() //类外实现无参构造函数15{16 _hour=0; //初始化过程,将成员变量初始化为017 _min=0;18 _sec=0;19}20void Clock::showTime() //类外实现成员函数21{22 cout<<setw(2)<<setfill('0')<<_hour<<":"23 <<setw(2)<<setfill('0')<<_min<<":"24 <<setw(2)<<setfill('0')<<_sec<<endl;25}26int main()27{28 Clock clock; //创建对象clock29 cout<<"clock:";30 clock.showTime(); //通过对象调用成员函数showTime()显示时间31 return 0;32}33
在例2-3中,第7行代码声明了一个无参构造函数;
第14~19行代码在类外实现构造函数,在构造函数体中直接将初始值赋给成员变量;
第28~30行代码在main()函数中创建了对象clock,并通过对象调用showTime()成员函数显示初始化时间。
对象clock的初始化时间为00:00:00,因为创建clock对象调用的是无参构造函数,无参构造函数将时、分、秒都初始化为0。
xxxxxxxxxx37123using namespace std;4class Clock //定义时钟类Clock5{6public:7 Clock(int hour, int min, int sec); //声明有参构造函数8 void showTime(); //用于显示时间的成员函数9private:10 int _hour; //声明表示小时的成员变量11 int _min; //声明表示分钟的成员变量12 int _sec; //声明表示秒的成员变量13};14Clock::Clock(int hour, int min, int sec) //类外实现有参构造函数15{16 _hour=hour; //初始化过程,将初始值直接赋值给成员变量17 _min=min;18 _sec=sec;19}20void Clock::showTime() //类外实现成员函数21{22 cout<<setw(2)<<setfill('0')<<_hour<<":"23 <<setw(2)<<setfill('0')<<_min<<":"24 <<setw(2)<<setfill('0')<<_sec<<endl;25}26int main()27{28 Clock clock1(10,20,30); //创建对象clock1,传入初始值29 cout<<"clock1:";30 clock1.showTime(); //通过对象调用成员函数showTime()显示时间31 Clock clock2(22,16,12); //创建对象clock2,传入初始值32 cout<<"clock2:";33 clock2.showTime(); //通过对象调用成员函数showTime()显示时间34 return 0;35}36
37在例2-4中,第7行代码声明了有参构造函数;第14~19行代码在类外实现有参构造函数,将参数赋值给成员变量,在创建对象时调用有参构造函数,用户可以传入初始值(参数)完成对象初始化。
第28~33行代码,创建了两个Clock对象clock1和clock2,这两个对象在创建时,传入了不同的参数,因此各个对象调用成员函数showTime()显示的初始化时间是不一样的。
需要注意的是,在实现构造函数时,除了在函数体中初始化成员变量,还可以通过“:”运算符在构造函数后面初始化成员变量,这种方式称为列表初始化,其格式如下所示:
xxxxxxxxxx41类::构造函数(参数列表): 成员变量1(参数1),成员变量2(参数2),...,成员变量n(参数n)2{3构造函数体4}
在例2-4中,使用列表初始化实现成员变量初始化的方式如下所示:
xxxxxxxxxx41Clock::Clock(int hour, int min, int sec):_hour(hour), _min(min), _sec(sec)2{3 //...4}xxxxxxxxxx41123using namespace std;4class Clock //定义时钟类Clock5{6public:7 //声明重载构造函数8 Clock(int hour, int min);9 Clock(int hour, int min, int sec=0); 10 void showTime(); //声明显示时间的成员函数11private:12 int _hour; //声明表示小时的成员变量13 int _min; //声明表示分钟的成员变量14 int _sec; //声明表示秒的成员变量15};16Clock::Clock(int hour, int min):_hour(hour),_min(min)17{18 cout<<"调用两个参数的构造函数"<<endl;19 _sec=10;20}21Clock::Clock(int hour, int min, int sec=0) //类外实现构造函数22{23 cout<<"调用三个参数的构造函数"<<endl;24 _hour=hour;25 _min=min;26 _sec=sec;27}28void Clock::showTime() //类外实现成员函数showTime()29{30 cout<<setw(2)<<setfill('0')<<_hour<<":"31 <<setw(2)<<setfill('0')<<_min<<":"32 <<setw(2)<<setfill('0')<<_sec<<endl;33}34int main()35{36 Clock clock(8,0); //创建对象clock,传入初始值37 cout<<"clock:";38 clock.showTime(); //通过对象调用成员函数显示时间39 return 0;40}41
运行例2-5时,编译器会报错。
在例2-5中,第8行代码声明了一个构造函数,该构造函数有两个参数;
第9行代码声明了一个构造函数,该构造函数有三个参数,且第三个参数有默认值;
第16~27行代码,在类外实现了这两个构造函数;
第36行代码,在main()函数中创建一个对象clock,传入两个参数,编译器无法确认调用的是第8行的构造函数还是第9行的构造函数,因此无法通过编译。
xxxxxxxxxx5612using namespace std;3class Birth //定义出生日期类Birth4{5public:6 Birth(int year,int month, int day);//构造函数7 void show(); //声明成员函数show()显示日期8private:9 int _year; 10 int _month;11 int _day;12};13//类外实现构造函数14Birth::Birth(int year, int month, int day)15 :_year(year),_month(month),_day(day)16{17 cout<<"Birth类构造函数"<<endl;18}19//类外实现show()函数20void Birth::show()21{22 cout<<"出生日期:"<<_year<<"-"<<_month<<"-"<<_day<<endl;23}24class Student //定义学生类Student25{26public:27 //构造函数28 Student(string name, int id, int year, int month, int day); 29 void show();30private:31 string _name;32 int _id;33 Birth birth;34};35//类外实现构造函数36Student::Student(string name, int id, int year, int month, int day)37 :birth(year,month,day)38{39 cout<<"Student类构造函数"<<endl;40 _name=name;41 _id=id;42}43//类外实现show()函数44void Student::show()45{46 cout<<"姓名:"<<_name<<endl;47 cout<<"学号:"<<_id<<endl;48 birth.show();49}50int main()51{52 Student stu("lili",10002,2000,1,1); //创建学生对象stu53 stu.show(); //显示学生信息54 return 0;55}56
在例2-6中,第3~12行代码定义了出生日期类Birth,该类有3个成员变量,分别是 _year、 _month、 _day,并且定义了有参数的构造函数;
第24~34行代码定义了学生类Student,该类有3个成员变量,分别是name、id、birth,其中birth是类Birth的对象。此外,Student类还定义了一个构造函数。由于成员对象birth的构造函数有3个参数,这3个参数要从类Student的构造函数中获取,因此Student类的构造函数共有5个参数。
第36~42行代码用于实现Student类的构造函数,birth成员对象必须通过“:”运算符在Student构造函数后面初始化,无法在Student构造函数体中赋值。
第52~53行代码,在main()函数中创建Student类对象stu,并通过对象stu调用成员函数show()显示学生信息。
创建对象stu时,先调用Birth类构造函数,之后才调用Student类构造函数。
xxxxxxxxxx40123using namespace std;4class Rabbit //定义兔子类Rabbit5{6public:7 Rabbit(string name,const char *pf); //声明构造函数8 void eat();9 ~Rabbit(); //声明析构函数10private:11 string _name; //声明表示兔子名字的成员变量12 char *_food; //声明表示兔子食物的成员变量13};14Rabbit::Rabbit(string name, const char* pf)15{16 cout<<"调用构造函数"<<endl;17 _name=name;18 _food=new char[50]; //为_food指针申请空间19 memset(_food,0,50); //初始化_food空间20 strcpy(_food,pf); //将参数pf指向的数据复制到_food中21}22void Rabbit::eat()23{24 cout<<_name<<" is eating "<<_food<<endl;25}26Rabbit::~Rabbit() //类外实现析构函数27{28 cout<<"调用析构函数,析构"<<_name<<endl;29 if(_food != NULL)30 delete []_food;31}32int main()33{34 Rabbit A("A","luobo");35 A.eat();36 Rabbit B("B","baicai");37 B.eat();38 return 0;39}40
在例2-7中,第4~13行代码,定义了一个兔子类Rabbit,该类有两个成员变量,分别是name、food,有一个构造函数、一个析构函数和一个普通成员函数eat();
第14~21行代码在类外实现构造函数。在实现构造函数时,由于第二个成员变量 _food是字符指针变量,因此在赋值时,要先使用 new运算符为 _food指针申请一块内存空间并初始化,再将参数pf指向的数据复制到 _food 指向的空间;
第22~25行代码在类外实现eat()函数;
第26~31行代码在类外实现析构函数,在析构函数中,使用delete运算符释放_food指向的内存空间。
第34~37行代码,在main()函数中,分别创建两个对象A和B,然后调用成员函数eat()实现吃食物的功能。
在创建对象的过程中,对象A与对象B除了对象本身所占用的内存空间,还各自拥有一块new运算符在堆上申请的空间。
程序运行结束后,编译器会调用析构函数释放对象资源,在释放时,先释放_food指向的内存空间,再释放对象所占用的内存空间。
程序调用析构函数析构的顺序是先析构对象B,再析构对象A。
xxxxxxxxxx4512using namespace std;3class Sheep //定义绵羊类Sheep4{5public:6 Sheep(string name,string color); //声明带参数构造函数7 Sheep(const Sheep& another); //声明拷贝构造函数8 void show(); //声明普通成员函数9 ~Sheep(); //声明析构函数10private:11 string _name; //声明表示绵羊名字的成员变量12 string _color; //声明表示绵羊颜色的成员变量13};14Sheep::Sheep(string name, string color)15{16 cout<<"调用构造函数"<<endl;17 _name=name;18 _color=color;19}20Sheep::Sheep(const Sheep& another) //类外实现拷贝构造函数21{22 cout<<"调用拷贝构造函数"<<endl;23 _name=another._name;24 _color=another._color;25}26void Sheep::show()27{28 cout<<_name<<" "<<_color<<endl;29}30Sheep::~Sheep()31{32 cout<<"调用析构函数"<<endl;33}34
35int main()36{37 Sheep sheepA("Doly","white");38 cout<<"sheepA:";39 sheepA.show();40 Sheep sheepB(sheepA); //使用sheepA初始化新对象sheepB41 cout<<"sheepB:";42 sheepB.show();43 return 0;44}45
在例2-8中,第3~13行代码定义了一个绵羊类Sheep,该类有两个成员变量,分别是 _name、 _color。此外,该类还声明了有参构造函数、拷贝构造函数、普通成员函数show()和析构函数;
第20~25行代码,在类外实现拷贝构造函数,在函数体中,将形参sheepA的成员变量值赋给类的成员变量;
第37~39行代码,在main()函数,创建了Sheep类对象sheepA,并输出sheepA的信息;
第40行代码创建Sheep类对象sheepB,并使用对象sheepA初始化对象sheepB,在这个过程中编译器会调用拷贝构造函数;
第41~42行代码输出对象sheepB的信息。
对象sheepA与对象sheepB的信息是相同的。程序首先调用构造函数创建了对象sheepA,然后调用拷贝构造函数创建了对象sheepB。程序运行结束之后,调用析构函数先析构对象sheepB,然后析构对象sheepA。
xxxxxxxxxx561234using namespace std;5class Sheep //定义绵羊类Sheep6{7public:8 Sheep(string name,string color,const char*home); //声明带参数构造函数9 Sheep(const Sheep& another); //声明拷贝构造函数10 void show(); //声明普通成员函数11 ~Sheep(); //声明析构函数12private:13 string _name; //声明表示绵羊名字的成员变量14 string _color; //声明表示绵羊颜色的成员变量15 char* _home; //声明表示绵羊家的成员变量16};17Sheep::Sheep(string name, string color,const char*home)18{19 cout<<"调用构造函数"<<endl;20 _name=name;21 _color=color;22 //为指针成员home分配空间,将形参home的内容复制到_home指向的空间23 int len=strlen(home)+1;24 _home=new char[len];25 memset(_home,0,len);26 strcpy(_home,home);27}28Sheep::Sheep(const Sheep& another) //类外实现拷贝构造函数29{30 cout<<"调用拷贝构造函数"<<endl;31 _name=another._name;32 _color=another._color;33 _home=another._home; //浅拷贝34}35void Sheep::show()36{37 cout<<_name<<" "<<_color<<" "<<_home<<endl;38}39Sheep::~Sheep()40{41 cout<<"调用析构函数"<<endl;42 if(_home!=NULL)43 delete []_home;44}45int main()46{47 const char *p = "beijing";48 Sheep sheepA("Doly","white",p);49 cout<<"sheepA:";50 sheepA.show();51 Sheep sheepB(sheepA); //使用sheepA初始化新对象sheepB52 cout<<"sheepB:";53 sheepB.show();54 return 0;55}56
运行例2-9中的程序,程序抛出异常,在第43行代码处触发异常断点。
例2-9是对例2-8的修改,在绵羊类Sheep中增加了一个char类型的指针变量成员 _home,用于表示绵羊对象的家。增加了 _hom e成员变量之后,类Sheep的构造函数、拷贝构造函数、析构函数都进行了相应修改。
第17~27行代码实现构造函数,在构造函数内部,首先为_home指针申请堆内存空间,然后调用strcpy()函数将形参home的内容复制到 _home指向的空间。
第28~34行代码实现拷贝构造函数,在拷贝构造函数内部,对指针成员只进行了简单的赋值操作,即浅拷贝。
第39~44行代码实现析构函数,在析构函数内部,使用delete运算符释放_home指向的内存空间。
第47~53行代码,在main()函数中,先创建对象sheepA,再创建对象sheepB,并用对象sheepA初始化对象sheepB。
在这个过程中,使用对象sheepA初始化对象sheepB是浅拷贝过程,因为对象sheepB的 _home指针指向的是对象sheepA的 _home指针指向的空间。
在浅拷贝过程中,对象sheepA中的 home指针与对象sheepB中的 _ home指针指向同一块内存空间。当程序运行结束时,析构函数释放对象所占用资源,析构函数先析构对象sheepB,后析构对象sheepA。在析构sheepB对象时释放了home指向的堆内存空间的数据,当析构sheepA时_home指向的堆内存空间已经释放,再次释放内存空间的资源,程序运行异常终止,即存储“beijing”的堆内存空间被释放了两次,因此程序抛出异常,这种现象被称重析构(double free)。
xxxxxxxxxx2912using namespace std;3class Person //定义类Person4{5public:6 Person(string name,int age,string addr); //声明带参构造函数7 const string _addr; //声明表示住址的常成员变量8 ~Person(); //声明析构函数9private:10 const string _name; //声明表示姓名的常成员变量11 const int _age; //声明表示年龄的常成员变量12};13//类外实现构造函数14Person::Person(string name,int age,string addr):15 _name(name),_age(age),_addr(addr)16{17 cout<<"初始化const修饰的成员变量"<<endl;18 cout<<"name:"<<_name<<endl;19 cout<<"age:"<<_age<<endl;20 cout<<"addr:"<<_addr<<endl;21}22Person::~Person(){} //类外实现析构函数23int main()24{25 Person p1("张三",18,"北大街");26 p1._addr="南大街";27 return 0;28}29
运行例2-10,编译器会报错
在例2-10中,第3~12行代码定义了一个类Person,该类有三个常成员变量: _name、 _age和 _addr。
第14~21行代码,在类外实现类的构造函数,类的常成员变量在构造函数中完成初始化,即创建对象时完成初始化。
第25行代码创建Person类对象p1,在创建对象时完成了三个常成员变量的初始化。这是创建对象后初始化常成员变量的唯一机会,常成员变量一旦初始化就不能再改变。第26行代码试图修改常成员变量,因此程序会报错。
xxxxxxxxxx4812using namespace std;3class Person //定义类Person4{5public:6 Person(string ,int,string,string); //声明带参构造函数7 ~Person(); //声明析构函数8 const string _addr; //声明表示住址的常成员变量9 void myInfor() const; //声明显示个人信息的常成员函数10 void myInfor(); //声明显示个人信息的普通成员函数11 void place(); //声明显示住址的普通成员函数12private:13 const string _name; //声明表示姓名的常成员变量14 const int _age; //声明表示年龄的常成员变量15 string _favFruit; //声明表示水果的普通成员变量16};17//有参构造函数初始化对象18Person::Person(string name,int age,string addr,string favFruit):19 _name(name), _age(age),_addr(addr),_favFruit(favFruit)20{21}22void Person::myInfor() const //类外实现常成员函数myInfor()23{24 //_favFruit="榴莲";25 //place();26 cout<<"我叫"<<_name<<"今年"<<_age<<"岁"<<"我喜欢吃"<<_favFruit<<endl; 27}28Person:: ~Person(){} //类外实现析构函数29void Person::myInfor() //类外实现普通成员函数myInfor()30{31 _favFruit="桃子";32 cout<<"我叫"<<_name<<"今年"<<_age33 <<"岁"<<"我喜欢吃"<<_favFruit<<endl; 34 place();35}36void Person::place() //类外实现普通成员函数place()37{38 cout<<"我住在"<<_addr<<endl;39}40int main()41{42 Person p1("张三",18,"北大街","苹果"); //创建对象p143 p1.myInfor(); //调用普通成员函数myInfor()44 const Person p2("李四",18,"南大街","橘子"); //创建常对象p245 p2.myInfor(); //调用常成员函数myInfor()46 return 0;47}48
在例2-11中,第3~16行代码定义了Person类,该类中定义了三个成员变量,其中 _name和 _age是常成员函数。
此外,第9~10行代码声明了Person类两个重载的成员函数myInfor(),第9行代码的myInfor()函数为常成员函数,第10行代码的myInfor()函数为普通成员函数。
第22~27行代码在类外实现常成员函数myInfor(),在函数内部,输出各个成员变量的值。需要注意的是,类的常成员函数不能修改成员变量的值,也不能调用非常成员函数,如第24~25行代码,如果取消注释,程序就会报错。
第29~35行代码在类外实现普通成员函数myInfor(),在函数内部,可以像第31行代码那样修改成员变量的值,也可以像第34行代码那样调用非常成员函数。
第42~43行代码创建对象p1,并通过p1调用myInfor()函数,由图2-16可知,对象p1调用的是普通成员函数m yInfor()。
第44~45行代码创建常对象p2,并通过p2调用myInfor(),由图2-16可知,常对象p2成功调用了常成员函数myInfor()。
xxxxxxxxxx3012using namespace std;3class Student //定义学生类Student4{5public:6 Student(string name); //声明带参构造函数7 ~Student(); //声明析构函数8 static int _sum; //声明表示学生总数的静态成员变量9private:10 string _name; //声明表示学生姓名的成员变量11};12//类外实现Student类带参数的构造函数13Student::Student(string name)14{15 this->_name=name;16 _sum++; 17}18Student::~Student(){} //类外实现析构函数19int Student::_sum = 0; //类外初始化静态成员变量_sum20int main()21{22 Student stu1("张三");23 Student stu2("李四");24 cout<<"人数是:"<<stu1._sum<<endl; //通过对象访问静态成员变量25 cout<<"人数是:"<<stu2._sum<<endl;26 cout<<"人数是"<<Student::_sum<<endl;//通过类访问静态成员变量27 cout<<"stu1的大小是:"<<sizeof(stu1)<<endl;28 return 0;29}30
在例2-12中,第3~11行代码定义了学生类Student,其中,第8行代码定义了静态成员变量sum。
第13~17行代码在类外部实现有参构造函数,每当创建对象时,sum的值自动加1,用于统计建立Student类对象的数目。
第19行代码在类外部初始化sum的值为0。
第22~23行代码创建了两个对象stu1和stu2。
第24~25行代码通过对象stu1和stu2访问静态成员变量 _sum,对象stu1和对象stu2访问到的静态成员变量 _sum值均为2。
第26行代码通过类的作用域访问静态成员变量sum,通过类的作用域访问到的静态成员变量sum值也为2。
第27行代码计算对象stu1的大小,对象stu1的大小为28,静态成员变量并不包含在对象中。
xxxxxxxxxx35123using namespace std;4class Point5{6public:7 Point (float x,float y);8 ~Point();9 static float getLen(Point &p1,Point &p2);10 static float _len; 11private:12 float _x;13 float _y;14};15float Point::_len=0;16Point::Point(float x=0,float y=0):_x(x),_y(y)17{18 cout<<"初始化坐标点"<<endl;19}20Point::~Point(){}21float Point::getLen(Point &p1,Point &p2)22{23 float x=abs(p1._x-p2._x);24 float y=abs(p1._y-p2._y);25 _len=sqrtf(x*x+y*y);26 return _len;27}28int main()29{30 Point p1(1,2);31 Point p2(6,8);32 cout<<Point::getLen(p1,p2)<<endl;33 return 0;34}35
例2-13中,第4~14行代码定义了类Point,其中,第9行代码定义了静态成员函数getLen(),用于获取两个坐标点之间的距离;第10行代码定义了静态成员变量len,用于存储两个坐标点之间的距离。
第16~19行代码在类外实现有参构造函数,初始化坐标点的值,默认值为0。
第21~27行代码,在类外实现getLen()函数,计算传入的两个坐标p1和p2之间的距离,并将结果保存到变量_len中。
第30~31行代码初始化坐标点p1和p2。
第32行代码调用getLen()函数计算两个坐标点之间的距离。
xxxxxxxxxx3312using namespace std;3class Circle4{5friend void getArea(Circle &circle); //声明普通函数getArea()为友元函数6private:7 float _radius;8 const float PI=3.14;9public:10 Circle(float radius);11 ~Circle();12};13Circle::Circle(float radius=0):_radius(radius)14{15 cout<<"初始化圆的半径为:"<<_radius<<endl;16}17Circle::~Circle(){}18void getArea(Circle &circle)19{20 //访问类中的成员变量21 cout<<"圆的半径是:"<<circle._radius<<endl;22 cout<<"圆的面积是"<<circle.PI*circle._radius*circle._radius<<endl;23 cout<<"友元函数修改半径:"<<endl;24 circle._radius=1;25 cout<<"圆的半径是:"<<circle._radius<<endl;26} 27int main()28{29 Circle circle(10);30 getArea(circle);31 return 0;32}33
在例2-14中,第3~12行代码定义了关于圆的类Circle,其中圆的半径_radius和圆周率PI是私有成员;
第5行代码在类中声明了友元函数getArea(),用于计算圆的面积;
第18~26行代码是getArea()函数的实现;
第29行代码创建对象circle,并初始化圆的半径为10;
第30行代码调用友元函数getArea()计算圆的面积,面积计算完成后,修改圆的半径为1。
普通函数作为友元函数访问了类中的私有成员,且具有修改私有成员的权限。
xxxxxxxxxx45123using namespace std;4class Point;5class Circle6{7public:8 float getArea(Point &p1,Point &p2); //声明计算面积的成员函数9private:10 const float PI=3.14;11};12class Point13{14//声明类Circle的getArea()函数为友元函数声明15friend float Circle::getArea(Point &p1,Point &p2); 16public:17 Point (float x,float y);18 ~Point();19private:20 float _x;21 float _y;22};23Point::Point(float x=0,float y=0):_x(x),_y(y) //实现Point类的构造函数24{25 cout<<"初始化坐标点"<<endl;26}27Point::~Point(){}28float Circle::getArea(Point &p1,Point &p2)29{30 double x=abs(p1._x-p2._x); //获取横轴坐标间的距离31 float y=abs(p1._y-p2._y); //获取纵轴坐标间的距离32 float len=sqrtf(x*x+y*y); //计算两个坐标点之间的距离33 cout<<"获取两个坐标点之间的距离是"<<len<<endl;34 return len*len*PI; //友元函数访问私有成员变量PI35}36int main()37{38 Point p1(5,5);39 Point p2(10,10);40 Circle circle;41 float area=circle.getArea(p1,p2);42 cout<<"圆的面积是:"<<area<<endl;43 return 0;44}45
在例2-15中,第4行代码声明类Point;
第5~11行代码定义了圆类Circle;
第12~22行代码定义了坐标点类Point,其中第15行代码将Circle类中的成员函数getArea()声明为友元函数。
第28~35行代码是getArea()函数的实现,函数的参数为Point类对象的引用,该函数计算两个坐标点距离的绝对值,然后以距离作为圆的半径,计算圆的面积后返回。
其中,第34行在计算圆的面积时访问了Circle类中的私有成员PI。
第38~39行代码初始化坐标点p1和p2。
第40~41行代码,创建对象circle,并通过对象circle调用友元函数getArea()计算圆的面积。
xxxxxxxxxx4412using namespace std;3class Time //定义Time类,描述时分秒4{5public:6 Time(int hour, int minute, int second); //声明带参构造函数7 friend class Date; //声明类Date为友元类8private:9 int _hour, _minute, _second;10};11class Date //定义Date类12{13public:14 Date(int year, int month, int day); //声明带参构造函数15 void showTime(Time& time); //声明显示时间的成员函数16private:17 int _year, _month, _day;18};19Date::Date(int year, int month, int day) //实现Date类构造函数20{21 _year = year;22 _month = month;23 _day = day;24}25void Date::showTime(Time& time)26{27 cout << _year << "-" << _month << "-" << _day28 << " " << time._hour << ":" << time._minute29 << ":" << time._second << endl;30}31Time::Time(int hour,int minute,int second) //实现Time类构造函数32{33 _hour = hour;34 _minute = minute;35 _second = second;36}37int main()38{39 Time time(17,30,20); //创建Time对象40 Date date(2019,10,31); //创建Date对象41 date.showTime(time); //调用showTime()显示年月日、时分秒信息42 return 0;43}44
在例2-16中,第3~10行代码定义了Tim e类,该类有三个成员变量hour、m inute和_second,分别表示时、分、秒;此外,Time类还声明了Date友元类;
第11~18行代码定义了Date类,Date类有三个成员变量 _year、 _month和 _day,分别用于表示年、月、日。
第19~30行代码在类外实现Date类的构造函数和成员函数showTime();
第31~36行代码在类外实现Tim e类的构造函数;第39~40行代码分别创建对象tim e和date;
第41行代码通过对象date调用成员函数showTime(),并以对象time作为参数。
为了让读者更好地理解和掌握继承的概念,下面通过案例演示派生类的定义与调用,如例4-1所示。
xxxxxxxxxx3512using namespace std;3class Animal //定义动物类Animal4{5public:6 void move(); //声明表示动物行为的成员函数move()7};8void Animal::move() //类外实现成员函数move()9{10 cout<<"动物行为"<<endl;11}12class Cat :public Animal //定义猫类Cat,公有继承动物类Animal13{14public:15 Cat(string name); //声明有参构造函数16 void walk(); //声明表示动物行为的成员函数walk()17private:18 string _name; //成员变量:表示名字19};20Cat::Cat(string name) //类外实现构造函数21{22 _name=name;23}24void Cat::walk() //类外实现普通成员函数walk()25{26 cout<<_name<<"会走"<<endl;27}28int main()29{30 Cat cat("猫"); //定义猫类对象cat31 cat.move(); //通过派生类对象调用基类成员函数32 cat.walk(); //通过派生类对象调用新增的成员函数33 return 0;34}35
在例4-1中,第3~7行代码定义了一个动物类Animal,该类中有一个成员函数move(),用于表示动物的行为;
第12~19行代码定义了一个猫类Cat,该类公有继承自Animal类;
第30行代码,在main()函数中创建了猫类对象cat;
第31行代码,通过对象cat调用基类成员函数move();
第32行代码,通过对象cat调用Cat类成员函数walk()。
在例4-1中,Cat类中并没有定义move()函数,但是Cat类继承了Animal类,它会继承Animal类的move()函数,因此Cat类对象能够调用move()函数。
下面通过案例演示类的公有继承,如例4-2所示。
xxxxxxxxxx5912using namespace std;3class Student //定义学生类Student4{5public:6void setGrade(string grade); //设置年级的成员函数7string getGrade(); //获取年级的成员函数8void setName(string name); //设置姓名的成员函数9string getName(); //获取姓名的成员函数10protected:11 string _grade; //保护成员:表示年级12private:13 string _name; //私有成员:表示姓名14};15void Student::setGrade(string grade) //类外实现setGrade()函数16{17 _grade=grade;18}19string Student::getGrade() //类外实现getGrade()函数20{21 return _grade;22}23void Student::setName(string name) //类外实现setName()函数24{25 _name=name;26}27string Student::getName() //类外实现getName()函数28{29 return _name;30}31class Undergraduate:public Student //大学生类公有继承学生类32{33public:34 Undergraduate(string major); //声明构造函数35 void show(); //声明显示大学生信息的成员函数36private:37 string _major; //私有成员:表示专业38};39//类外实现构造函数40Undergraduate::Undergraduate(string major)41{42 _major=major;43}44void Undergraduate::show() //类外实现show()函数45{46 cout<<"姓名:"<<getName()<<endl; //派生类调用基类成员函数47 cout<<"年级:"<<_grade<<endl; //派生类访问继承的基类成员变量48 cout<<"专业:"<<_major<<endl; //派生类访问新增成员49}50int main()51{52 //创建大学生类对象stu53 Undergraduate stu("计算机信息工程"); 54 stu.setGrade("大三"); //派生类对象调用基类成员函数设置年级55 stu.setName("zhangsan"); //派生类对象调用基类成员函数设置姓名56 stu.show(); //派生类对象调用新增成员函数显示学生信息57 return 0;58}59
在例4-2中,第3~14行代码定义了学生类Student,该类声明了私有成员变量_name表示姓名,保护成员变量 _grade表示年级。Student类还定义了4个公有成员函数,分别用于设置、获取学生姓名和年级。
第31~38行代码定义大学生类Undergraduate公有继承Student类。Undergraduate类定义了私有成员变量 _major表示学生专业,此外,还定义了构造函数和显示学生信息的show()函数。
第53~55行代码,在main()函数中创建Undergraduate类对象stu,并通过对象stu调用基类的setGrade()函数、setNam e()函数,用来设置学生的年级和姓名。
第56行代码通过对象stu调用show()函数显示学生信息。
需要注意的是,在例4-2中,第46~47行代码,在Undergraduate类的show()函数内部直接访问了从基类继承过来的保护成员 _grade,因为Undergraduate类是公有继承Student类, _grade在派生类Undergraduate中也是保护成员,所以可以通过成员函数show()访问。但是,show()函数无法直接访问从基类继承过来的 _name成员,因为 _name是基类的私有成员,在派生类中, _name变成了派生类的不可访问成员。所以在show()函数中只能通过基类的公有成员函数getName()访问 _name成员。如果在show()函数中直接访问从基类继承过来的 _name成员,程序会报错。
例如,若在show()函数中添加如下代码:
xxxxxxxxxx11cout << _name << endl;再次运行程序,编译器会报错。
下面通过案例演示基类与派生类之间的类型兼容,如例4-3所示。
xxxxxxxxxx4912using namespace std;3class Base //定义基类Base4{5public:6 Base(); //Base类构造函数7 void show(); //Base类普通成员函数show()8protected:9 string _name; //Base类保护成员变量_name10};11Base::Base() //类外实现基类构造函数12{13 _name="base";14}15void Base::show() //类外实现show()函数16{17 cout<<_name<<" Base show()"<<endl;18}19class Derive : public Base //Derive类公有继承Base类20{21public:22 Derive(); //Derive类构造函数23 void display(); //Derive类普通成员函数display()24};25Derive::Derive() //类外实现派生类构造函数26{27 _name="derive"; //_name成员从Base类继承而来28}29void Derive::display() //类外实现display()函数30{31 cout<<_name<<" Derive show()"<<endl;32}33void func(Base* pbase) //定义普通函数func(),参数为基类指针34{35 pbase->show();36}37int main()38{39 Derive derive; //创建Derive类对象derive40 Base base=derive; //使用对象derive为Base类对象base赋值41 Base &qbase=derive; //使用对象derive为Base类对象的引用qbase赋值42 Base *pbase=&derive; //使用对象derive的地址为Base类指针pbase赋值43 base.show(); //通过Base类对象调用show()函数44 qbase.show(); //通过Base类对象引用调用show()函数45 pbase->show(); //通过Base类指针调用show()函数46 func(&derive); //取对象derive的地址作为func()函数的参数47 return 0;48}49
在例4-3中,第3~10行代码定义了Base类,该类有一个保护成员变量_name;此外Base类还定义了构造函数和普通成员函数show()。
第19~24行代码定义了Derive类,Derive类公有继承Base类;Derive类中定义了构造函数和普通成员函数display()。
第33~36行代码定义了一个函数func(),该函数有一个Base类的指针作为参数,在函数内部,通过Base类指针调用show()函数。
第39行代码,在main()函数中创建了Derive类对象derive;
第40行代码创建Base类对象base,使用对象derive为其赋值;
第41行代码创建Base类对象的引用,使用derive对象为其赋值;
第42行代码定义Base类指针,取对象derive的地址为其赋值。
第43~45行代码分别通过Base类对象、Base类对象的引用、Base类指针调用show()函数;
第46行代码调用func()函数,并取对象derive的地址作为实参传递。
下面通过案例演示派生类构造函数与析构函数的定义与调用,如例4-4所示。
xxxxxxxxxx9812using namespace std;3class Engine //定义发动机类Engine4{5public:6 Engine(string type,int power); //发动机构造函数7 void show(); //发动机普通成员函数show8 ~Engine(); //发动机析构函数9private:10 string _type; //成员_type表示型号11 int _power; //成员_power表示功率12};13Engine::Engine(string type, int power) //类外实现构造函数14{15 cout<<"调用发动机Engine构造函数"<<endl;16 _type=type;17 _power=power;18}19void Engine::show() //类外实现show()函数20{21 cout<<"发动机型号:"<<_type<<",发动机功率:"<<_power<<endl;22}23Engine::~Engine() //类外实现析构函数24{25 cout<<"调用发动机Engine析构函数"<<endl;26}27class Vehicle //定义交通工具类Vehicle28{29public:30 Vehicle(string name); //交通工具类构造函数31 void run(); //交通工具类普通成员函数run()32 string getName(); //交通工具类普通成员函数getName()33 ~Vehicle(); //交通工具类析构函数34private:35 string _name; //成员_name表示交通工具种类36};37Vehicle::Vehicle(string name) //类外实现构造函数38{39 cout<<"调用交通工具Vehicle构造函数"<<endl;40 _name=name;41}42void Vehicle::run() //类外实现run()函数43{44 cout<<_name<<"正在行驶中"<<endl;45}46string Vehicle::getName() //类外实现getName()函数47{48 return _name;49}50Vehicle::~Vehicle() //类外实现析构函数51{52 cout<<"调用交通工具Vehicle析构函数"<<endl;53}54//定义小汽车类Car,公有继承交通工具类Vehicle55class Car :public Vehicle56{57public:58 //Car类构造函数,其参数包括了成员对象、基类成员变量、新增成员变量的参数59 Car(int seats,string color,string type, int power,string name);60 void brake(); //小汽车类普通成员函数brake()61 void display(); //小汽车类普通成员函数display()62 ~Car(); //小汽车类析构函数63 Engine engine; //公有成员变量,Engine类对象64private:65 int _seats; //成员_seats表示座位数量66 string _color; //成员_color表示颜色67};68//类外实现构造函数,后面使用“:”运算符调用成员对象构造函数、基类构造函数69Car::Car(int seats, string color, string type, int power, string name):70 engine(type,power),Vehicle(name)71{72 cout<<"调用小汽车Car构造函数"<<endl;73 _seats=seats;74 _color=color;75}76void Car::brake() //类外实现brake()函数77{78 cout<<getName()<<"停车"<<endl;79}80void Car::display() //类外实现display()函数81{82 cout<<getName()<<"有"<<_seats<<"个座位,"<<"颜色为"<<_color<<endl;83}84Car::~Car() //类外实现析构函数85{86 cout<<"调用小汽车Car析构函数"<<endl;87}88int main()89{90 Car car(5,"red","EA113",130,"passat"); //创建小汽车类对象car91 car.run(); //调用基类的run()函数92 car.brake(); //调用brake()函数93 car.display(); //调用display()函数94 //通过成员对象engine调用Engine类的show()函数,显示发动机信息95 car.engine.show();96 return 0;97}98
在例4-4中,第3~12行代码定义了发动机类Engine,该类定义了两个私有成员变量type和power,分别表示发动机型号和功率;此外,Engine类还声明了构造函数、普通成员函数show()和析构函数。其中,show()函数用于显示发动机信息。
第13~26行代码,在Engine类外实现各个函数。
第27~36行代码定义了交通工具类Vehicle,该类有一个私有成员变量_name,用于表示交通工具的名称;此外,Vehicle类还声明了构造函数、普通成员函数run()、普通成员函数getNam e()和析构函数。
第37~53行代码在Vehicle类外实现各个函数。
第55~67行代码定义小汽车类Car,Car类公有继承Vehicle类。Car类定义了两个私有成员变量 _seats和 _color,分别表示小汽车的座位数量和颜色。此外,Car类还包含Engine类对象engine,该成员对象为公有成员变量。除了成员变量,Car类还声明了构造函数、普通成员函数brake()、普通成员函数display()和析构函数。
第69~87行代码在Car类外实现各个函数。其中,第69~75行代码实现Car类的构造函数,Car类的构造函数有5个参数,用于初始化成员对象engine、基类Vehicle对象和本类对象。
第90行代码,在main()函数中创建Car类对象car,传入5个参数。
第91~93行代码通过对象car调用基类的run()函数、本类的brake()函数和display()函数实现小汽车各种功能。
第95行代码通过对象car中的公有成员对象engine调用Engine类的show()函数,显示小汽车发动机信息。
派生类构造函数完成了本类对象、成员对象和基类对象的初始化。创建派生类对象时,先调用基类构造函数,再调用成员对象的构造函数,最后调用派生类的构造函数。在析构时,先调用派生类的析构函数,再调用成员对象的析构函数,最后调用基类的析构函数。
下面通过案例演示在派生类中隐藏基类成员函数的方法,如例4-5所示。
xxxxxxxxxx3012using namespace std;3class Vehicle //定义交通工具类Vehicle4{5public:6 void run(); //交通工具类普通成员函数run()7};8void Vehicle::run() //类外实现run()函数9{10 cout<<"基类run()函数:行驶"<<endl;11}12class Car :public Vehicle //定义小汽车类Car,公有继承交通工具类Vehicle13{14public:15 void run(); //小汽车类普通成员函数run()16};17void Car::run() //类外实现brake()函数18{19 cout<<"小汽车需要燃烧汽油,行驶速度快"<<endl;20}21int main()22{23 Car car; //创建小汽车类对象car24 car.run(); //调用派生类的run()函数25 car.Vehicle::run(); //通过基类名与作用域限定符调用基类run()函数26 Vehicle* pv=&car;27 pv->run(); //基类指针调用基类run()函数28 return 0;29}30
在例4-5中,第3~7行代码定义了交通工具类Vehicle,该类声明了普通成员函数run(),用于实现交通工具的行驶功能。
第8~11行代码在类外实现run()函数。
第12~16行代码定义了小汽车类Car公有继承交通工具类Vehicle,该类也定义了run()函数,对基类的run()函数进行改写。
第17~20行代码实现Car类的run()函数。
第23行代码,在main()函数中创建Car类对象car。
第24行代码,通过对象car调用run()函数,此次调用的是Car类改写后的run()函数。
第25行代码,通过作用域限定符“::”调用基类的run()函数。
第26~27行代码,定义Vehicle类指针pv,取对象car的地址为其赋值。通过pv指针调用run()函数,只能调用Vehicle类的run()函数,无法调用派生类Car改写的run()函数。
需要注意的是,只要是同名函数,无论参数列表和返回值类型是否相同,基类同名函数都会被隐藏。若基类中有多个重载函数,派生类中有同名函数,则基类中所有同名函数在派生类中都会被隐藏。
下面通过案例演示多继承派生类构造函数与析构函数的定义与调用,如例4-6所示。
xxxxxxxxxx3712using namespace std;3class Wood //木材类Wood4{5public:6 Wood(){cout<<"木材构造函数"<<endl; } 7 ~Wood(){cout<<"木材析构函数"<<endl; }8};9class Sofa //沙发类Sofa10{11public:12 Sofa(){cout<<"沙发构造函数"<<endl; }13 ~Sofa(){cout<<"沙发析构函数"<<endl; }14 void sit(){cout<<"Sofa用来坐..."<<endl; }15};16class Bed //床类Bed17{18public:19 Bed(){cout<<"床的构造函数"<<endl; }20 ~Bed(){cout<<"床的析构函数"<<endl; }21 void sleep(){cout<<"Bed用来睡觉..."<<endl; }22};23class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类24{25public:26 Sofabed(){cout<<"沙发床构造函数"<<endl; }27 ~Sofabed(){cout<<"沙发床析构函数"<<endl; }28 Wood pearwood; //Wood对象,梨木29};30int main()31{32 Sofabed sbed; //创建沙发床对象sbed33 sbed.sit(); //通过sbed调用基类Sofa的sit()函数34 sbed.sleep(); //通过sbed调用基类Bed的sleep()函数35 return 0;36}37
在例4-6中,第3~8行代码定义了木材类Wood,该类定义了构造函数与析构函数。
第9~15行代码定义了沙发类Sofa,该类定义了构造函数、析构函数和普通成员函数sit()。
第16~22行代码定义了床类Bed,该类定义了构造函数、析构函数和普通成员函数sleep()。
第23~29行代码定义了沙发床类Sofabed,该类公有继承Sofa类和Bed类。Sofabed类中包含Wood类对象pearwood;此外,Sofabed类还定义了构造函数与析构函数。
第32行代码,在main()函数中创建了Sofabed类对象sbed;
第33行代码通过对象sbed调用基类Sofa的sit()函数;
第34行代码通过对象sbed调用基类Bed的sleep()函数。
对象sbed成功调用了基类的sit()函数与sleep()函数。在对象sbed创建和析构的过程中,构造函数的调用顺序如下:按照基类的继承顺序,先调用Sofa类构造函数,再调用Bed类构造函数;调用完基类构造函数之后,调用派生类Sofabed中的成员对象(Wood类)的构造函数,最后调用派生类Sofabed的构造函数。在析构时,析构函数的调用顺序与构造函数相反。
下面通过案例演示派生类对象访问基类同名成员函数时产生的二义性问题,如例4-7所示。
xxxxxxxxxx2412using namespace std;3class Sofa //沙发类Sofa4{5public:6 void rest(){cout<<"沙发可以坐着休息"<<endl; }7};8class Bed //床类Bed9{10public:11 void rest(){cout<<"床可以躺着休息"<<endl; }12};13class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类14{15public:16 void function(){cout<<"沙发床综合了沙发和床的功能"<<endl; }17};18int main()19{20 Sofabed sbed; //创建沙发床对象sbed21 sbed.rest(); //通过sbed调用rest()函数22 return 0;23}24
运行例4-7程序,编译器报错。程序错误原因是rest()函数调用不明确。
在例4-7中,第3~7行代码定义了沙发类Sofa,该类定义了公有成员函数rest()。
第8~12行代码定义了床类Bed,该类也定义了公有成员函数rest()。
第13~17行代码定义了沙发床类Sofabed,该类公有继承Sofa类和Bed类。
第20行代码,在main()函数中创建Sofabed类对象sbed。第21行代码通过对象sbed调用基类的rest()函数,由于基类Sofa和基类Bed中都定义了rest()函数,因此对象sbed调用rest()函数时会产生二义性。
在派生类Sofabed中有两个rest()函数,因此在调用时产生了歧义。多继承的这种二义性可以通过作用域限定符“::”指定调用的是哪个基类的函数,可以将例4-7中第21行代码替换为如下两行代码:
xxxxxxxxxx21sbed.Sofa::rest(); //调用基类Sofa的rest()函数2sved.Bed::rest(); //调用基类Bed的rest()函数
通过上述方式明确了所调用的函数,即可消除二义性。这需要程序设计者了解类的继承层次结构,相应增加了开发难度。
下面通过案例演示多重继承中成员变量产生的访问二义性问题,如例4-8所示。
xxxxxxxxxx6212using namespace std;3class Furniture //家具类Furniture4{5public:6 Furniture(string wood); //Furniture类构造函数7protected:8 string _wood; //成员变量_wood,表示材质9};10Furniture::Furniture(string wood) //类外实现构造函数11{12 _wood=wood;13}14class Sofa:public Furniture //沙发类Sofa,公有继承Furniture类15{16public:17 Sofa(float length,string wood); //Sofa类构造函数18protected:19 float _length; //成员变量_length,表示沙发长度20};21//类外实现Sofa类构造函数22Sofa::Sofa(float length,string wood):Furniture(wood)23{24 _length=length;25};26class Bed:public Furniture //床类Bed,公有继承Furniture类27{28public:29 Bed(float width, string wood); //Bed类构造函数30protected:31 float _width; //成员变量_width,表示床的宽度32};33//类外实现Bed类构造函数34Bed::Bed(float width, string wood):Furniture(wood)35{36 _width=width;37}38class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类39{40public:41 //构造函数42 Sofabed(float length,string wood1, float width,string wood2);43 void getSize(); //成员函数getSize(),获取沙发床大小44};45//类外实现Sofabed类构造函数46Sofabed::Sofabed(float length, string wood1, float width, string wood2):47 Sofa(length,wood1),Bed(width,wood2)48{49}50void Sofabed::getSize() //类外实现getSize()函数51{52 cout<<"沙发床长"<<_length<<"米"<<endl;53 cout<<"沙发床宽"<<_width<<"米"<<endl;54 cout<<"沙发床材质为"<< _wood<<endl;55}56int main()57{58 Sofabed sbed(1.8,"梨木",1.5,"檀木"); //创建Sofabed类对象sbed59 sbed.getSize(); //调用getSize()函数获取沙发床信息60 return 0;61}62
运行例4-8,编译器报错。
在例4-8中,第3~9行代码定义了家具类Furniture,该类定义了保护成员变量_wood,表示家具材质,还定义了构造函数。
第10~13行代码在Furniture类外实现构造函数。
第14~20行代码定义了沙发类Sofa公有继承Furniture类,Sofa类定义了保护成员变量length,表示沙发长度。此外,Sofa类还定义了构造函数。
第22~25行代码在Sofa类外实现构造函数。
第26~32行代码定义了床类Bed公有继承Furniture类,Bed类定义了保护成员变量 _width,表示床的宽度;此外,Bed还定义了构造函数。
第34~37行代码在Bed类外实现构造函数。
第38~44行代码定义了沙发床类Sofabed,该类公有继承Sofa类和Bed类。
基类Furniture的成员变量 _wood在Sofabed类中有两份拷贝,分别通过继承Sofa类和Bed类获得。创建Sofabed类对象时,两份拷贝都获得数据。
在例4-8中,第58~59行代码,创建Sofabed类对象sbed,并通过对象sbed调用getSize()函数获取沙发床信息。在getSize()函数中,第54行代码通过cout输出wood成员值,由于sbed对象中有两个wood成员值,在访问时出现了二义性,因此编译器报错。为了避免访问_wood成员产生的二义性,必须通过作用域限定符“::”指定访问的是哪个基类的 _wood成员。可以将例4-8中的第54行代码替换为如下两行代码:
xxxxxxxxxx21cout << "沙发床的材质为" << Sofa::_wood << endl ;2cout << "沙发床的材质为" << Bed::_wood << endl ;下面通过修改例4-8的代码,让Sofa类和Bed类虚继承Furniture类,演示虚继承的作用,如例4-9所示。
xxxxxxxxxx6212using namespace std;3class Furniture //家具类Furniture4{5public:6 Furniture(string wood); //Furniture类构造函数7 protected:8 string _wood; //成员变量_wood,表示材质9};10Furniture::Furniture(string wood) //类外实现构造函数11{12 _wood=wood;13}14class Sofa:virtual public Furniture //沙发类Sofa,虚继承Furniture类15{16public:17 Sofa(float length,string wood); //Sofa类构造函数18protected:19 float _length; //成员变量_length,表示沙发长度20};21//类外实现Sofa类构造函数22Sofa::Sofa(float length,string wood):Furniture(wood)23{24 _length=length;25};26class Bed:virtual public Furniture //床类Bed,虚继承Furniture类27{28public:29 Bed(float width, string wood); //Bed类构造函数30protected:31 float _width; //成员变量_width,表示床的宽度32};33//类外实现Bed类构造函数34Bed::Bed(float width, string wood):Furniture(wood)35{36 _width=width;37}38class Sofabed:public Sofa,public Bed //Sofabed类,公有继承Sofa类和Bed类39{40public:41 //构造函数42 Sofabed(float length,string wood1, float width,string wood2);43 void getSize(); //成员函数getSize(),获取沙发床大小44};45//类外实现Sofabed类构造函数46Sofabed::Sofabed(float length, string wood1, float width, string wood2):47 Sofa(length,wood1),Bed(width,wood2),Furniture(wood1)48{49}50void Sofabed::getSize() //类外实现getSize()函数51{52 cout<<"沙发床长"<<_length<<"米"<<endl;53 cout<<"沙发床宽"<<_width<<"米"<<endl;54 cout<<"沙发床材质为"<<_wood<<endl;55}56int main()57{58 Sofabed sbed(1.8,"梨木",1.5,"檀木"); //创建Sofabed类对象sbed59 sbed.getSize(); //调用getSize()函数获取沙发床大小60 return 0;61}62
在例4-9中,第14~20行代码定义了沙发类Sofa,Sofa类虚继承Furniture类。
第26~32行代码定义床类Bed,Bed类虚继承Furniture类。
第38~44行代码定义沙发床类Sofabed,Sofabed公有继承Sofa类和Bed类。
第58~59行代码,创建Sofabed类对象sbed,并通过对象sbed调用getSize()函数获取沙发床大小。
在Sofabed类的getSize()函数中,第54行代码直接访问了_wood成员,但编译器并没有报错。这是因为在对象sbed中只有一个 _wood成员数据。