Chapter2
1.内联函数
内联函数不需要开辟空间,它直接嵌入到主函数中,以空间换时间,一般是一个函数调用次数过多时,会将它设置为内联函数
2.默认参数值
带有默认参数值的函数
3.函数重载
函数重载(和Java差不多)
函数的重载与带默认值的函数一起使用时,有可能引起二义性
4.作用域运算符
如果全局变量和局部变量同名,那么局部变量在其作用域内具有较高的优先权。如果希望在局部变量的作用域内使用全局变量,可以在该变量前面加上"::"
5.强制类型转换
6.new,delete
在C++中用运算符new和delete更简单的进行内存的分配和释放,用new分配的存储空间不会自动释放,只能通过delete释放
7.引用格式
类型 &引用名=已定义的变量名,此时的&是引用声明符,不是地址符
引用就相当于给已定义的变量名起一个别名,一个别名只能代表一个变量,不能建立引用的数组,不能建立引用的引用
使用引用返回函数值:
#include "iostream"
using namespace std;
int a[]={1,3,5,4,9};
int &index(int);//此处是函数原型,声明函数返回一个整数类型的引用
int main(){
index(2)=25;//
cout<<index(2);
return 0;
}
int &index(int i){
return a[i];
}
代码实例部分:
#include "iostream"
#include "cstring"
using namespace std;
int avar;
class Person{
public:
Person() {}
Person(char *n, int a, char s){
name = n;
age = a;
sex = s;
cout<<"Object has been constructed"<<endl;
}
void eat(){
cout<<"Human beings must eat numerous food every day"<<endl;
}
void display(){
cout<<"The name is "<<name<<",his age is "<<age<<sex<<endl;
}
private:
char *name;
int age;
string sex;
};
//内联函数,带有默认参数值的函数
inline int mul(int x = 3,int y = 4,int z = 5);
int main(){
//局部变量和全局变量,作用域运算符
int avar;
avar = 25;
::avar = 10;
cout<<avar<<" "<<::avar<<endl;
//强制类型转换格式,下面两行是等价的
double q = (double)avar;
double w = double (avar);
Person p1,p2("张驰锋" ,18,'n');
p1.eat();
p2.display();
//带有默认参数值的函数
int v,a,b,c;
v = mul();
a = mul(1,2,3);
b = mul(1,2);
c = mul(1);
cout<<v<<' '<<a<<' '<<b<<endl;
cout<<c;
//使用new运算符开辟一个内存空间并把首地址赋给ptr
int *ptr;
ptr = new int;
*ptr = 10;
//和上面的两行等价
ptr = new int(10);
cout<<* ptr<<endl;
//使用delete运算符释放指针ptr指向的内存空间
delete ptr;
//&
int &r = avar;//此时&是引用声明符
cout<<r<<&r<<endl;//此时&是地址符,r=25,&r代表地址
return 0;
}
//内联函数
inline int mul(int i,int j,int z){
return i*j*z;
}
引用作为参数的返回值
//
// Created by Administrator on 2021/11/16.
//
#include "iostream"
using namespace std;
int a[]={1,3,5,4,9};
int &index(int);//此处是函数原型,声明函数返回一个整数类型的引用
int main(){
index(2)=25;//
cout<<index(2);
return 0;
}
int &index(int i){
return a[i];
}
Chapter3
1.结构体类型
结构体类型是自定义类型,在C++中结构体不仅可以含有不同类型,而且还可以含有函数,C++提供了一种比结构体更加安全有效的数据类型——类,类的成员主要包括数据成员和成员函数,类的变量叫对象。C++将类分成两大类:私有成员(private)和公有变量(public)。私有成员只能被类内的成员函数访问,而不能被类外的对象访问;公有成员既可被类内的成员函数访问,也可被类外的对象访问。在默认情况下(也就是说没有指定属于私有或公有时),类中的成员是私有的,但是在结构体中是公有的
//
// Created by Administrator on 2021/11/16.
//
#include "iostream"
#include "cstring"
using namespace std;
struct People{
int age;
string name;
string sex;
void init(int a,string n,string s){
age = a;
name = n;
sex = s;
}
void eat(){
cout<<"People must eat food every day"<<endl;
}
};
int main(){
People zzz;
zzz.init(18,"张三","男");
zzz.eat();
return 0;
}
2.使用变量定义
在C++中使用变量定义数组必须用动态分配
int *a = new int[n];
3.类内原型,类外定义
类内写原型,类外定义
//类内写原型
void setpoint(int,int);
//类外定义
void Point::setpoint(int a,int b){
}
若将成员函数直接定义在内部,C++编译器会将函数作为内联函数处理,此时是隐式定义内联函数,显示定义内联函数如下:
//类内写原型
inline void setpoint(int,int);
//类外定义
inline void Point::setpoint(int a,int b){
}
**4.**对象的定义
//法一
class Person{
public:
Person() {}
Person(char *n, int a, char s){
name = n;
age = a;
sex = s;
cout<<"Object has been constructed"<<endl;
}
void eat(){
cout<<"Human beings must eat numerous food every day"<<endl;
}
void display(){
cout<<"The name is "<<name<<",his age is "<<age<<sex<<endl;
}P1,P2;
//法二
int main(){
Person p1,p2;
return 0;
}
**5.**对象中成员的访问
1.普通访问:对象名.数据成员名
2.通过指向对象的指针访问对象中的成员
3.通过对象的引用访问成员
6.类的作用域
类的作用域就是指在类的声明中的一对花括号所形成的作用域,一般来说,公有成员是类的对外接口
**7.**构造函数
特点:
1.构造函数是一种特殊的成员函数,它主要用于为对象分配空间,进行初始化
2.构造函数的名字必须与类名相同,
3.它可以有任意类型的参数,但不能具有返回值类型
形式:
1.类名 对象名[(实参表)]
2.类名 *指针变量名 = new 类名[(实参表)]
//这是一种使用new运算符动态建立对象的方式,访问用new动态建立的对象一般通过指针访问
用成员初始化列表对数据成员初始化
**8.**析构函数
析构函数也是一种特殊的成员函数,它执行与构造函数相反的函数
形式:~类名();
构造函数和析构函数都可以类内写原型,类外定义
5,6,7的代码
Created by Administrator on 2021/11/16.//#include "iostream"using namespace std;class Person{public: Person() : rx(age),PI(3.14) {}; Person(int a):age(a),rx(age),PI(3.14){ age = a; };public: int age; int ℞ const double PI;};int main(){ //用成员初始化列表对引用类型和const修饰的数据成员初始化 Person p5(19); //使用new动态创立对象 Person *p4 = new Person(18); Person p1,*p2; p2 = &p1; Person &p3 = p1; //访问new动态创建的对象要用指针的形式 cout<<p4->age<<endl; //下面三行是等价的 cout<<p1.age<<endl;//方法一.运算符访问 cout<<p2->age<<endl;//方法二,指针访问 cout<<(*p2).age<<endl;//方法二,指针访问 cout<<p3.age<<endl;//方法三,引用访问 return 0;}
9.对象数组
定义一个一维数组格式如下:
类名 数组名[下标表达式];
Complex com[10];
//定义类Complex的对象数组com,含有10个对象数组元素
访问单个数组元素:
com[i].get();
**10.**对象指针
声明对象指针:
类名 *对象指针名;
1.用指针访问单个成员
Complex c1;
Complex *c2;
c2 = &c1;
c2->set();
2.用对象指针访问对象数组
Complex *p;
Complex p1[2];
p = p1;
表示把对象数组的第一个元素的地址(即数组的地址)赋给对象指针变量p。一般而言,当指针加1或者减1时,它总是指向其基本类型中相邻的一个元素。
#include "iostream"using namespace std;class Person{public: void set(int a){ age = a; } void show(){ cout<<age<<endl; }private: int age;};int main(){ Person p1; Person *p2; Person p[2]; p[0].set(1); p[1].set(2); p2=p; p2->show(); p2++; p2->show(); p1.set(3); p1.show(); p2=&p1; p2->show(); (*p2).show(); return 0;}
**11.**值传递的三种方式
值传递 | 形参 | 实参 |
---|
引用传递 | 类名 &对象名 | 对象名 | 指针传递 | 类名 *对象名 | &对象名 | 对象传递 | 类名 对象名 | 对象名 |
#include "iostream"using namespace std;class Tr{public: Tr(int n){ i = n; } void set_i(int n){ i = n; } int get_i(){ return i; }private: int i;};void sqr_it(Tr ob){//对象传递 ob.set_i(ob.get_i()*ob.get_i()); cout<<ob.get_i()<<endl;}void sqr_it(Tr *ob){//指针传递 ob->set_i(ob->get_i()*ob->get_i()); cout<<ob->get_i()<<endl;}void sqr_it1(Tr &ob){//引用传递 ob.set_i(ob.get_i()*ob.get_i()); cout<<ob.get_i()<<endl;}int main(){ Tr obj(10); //对象传递 sqr_it(obj); //指针传递 sqr_it(&obj); //引用传递 sqr_it1(obj); return 0;}
**12.**对象的赋值和复制
对象赋值语句:两个对象的类型必须相同,赋值之后仅仅使对象中数据成员相同,而两个对象仍是分离的(再调用函数去设置对象,不会对另一个对象有影响),当类中存在指针时,可能会产生错误
拷贝构造函数:Tr obj1(obj) //对象作实参,引用作形参
拷贝构造函数也是一种构造函数,该函数只有一个参数,并且是同类对象的引用,每个类都必须有一个拷贝构造函数,可以是用户自定义的,也可以是默认的
自定义拷贝构造函数:
类名::类名(const 类名 &对象名)
{
? //拷贝构造函数的函数体
}
调用拷贝构造函数的一般形式:
1.类名 对象2(对象1);//代入法
2.类名 对象2=对象1;//赋值法
调用拷贝构造函数的三种情况:
1.当用一个类的对象去初始化该类的另一个对象时,直接用代入法或者赋值法调用
2.当函数的形参是类的对象时,在调用函数进行形参和实参结合时
3.当函数值的返回值是类的对象,在函数调用完即将返回值带回函数调用处时
Created by Administrator on 2021/11/17.//#include "iostream"using namespace std;class Person{public: void set(int a){ age = a; } void show(){ cout<<age<<endl; }private: int age;};int main(){ Person p1; p1.set(10); //调用拷贝构造函数,且下面两行等价 Person p2(p1); Person p3 = p1; //此时p1,p2,p3的age是一样的 return 0;}
**13.**静态成员
为了实现一个类的多个对象之间的数据共享,C++提出了静态成员的变量
1.定义静态数据成员的格式:
? static 数据类型 数据成员名
a.静态成员的初始化一般要在类外进行,而且应在定义对象之前,一般在主函数main之前,初始化格式:
数据类型 类名::静态数据成员名=初始值;
b.静态数据成员属于类(准确的说,是类对象的集合),而不像普通数据成员那样属于某一对象,因此可以使用“类名::”访问静态的数据成员,格式:
类名::静态数据成员名
c.用对象访问静态数据成员格式:在类的外部只能访问公有的静态数据成员
对象名.静态数据成员名;
对象指针->静态数据成员名;
d.私有静态数据成员不能在类外直接访问,必须通过公有的成员函数访问,公有的静态数据成员可以在对象定义之前被访问
e.C++支持静态数据成员的一个重要原因是可以不必使用全局变量
2.静态成员函数属于整个类,是该类所有对象共享的成员函数,而不属于类中的某个对象,定义静态成员函数的格式:
static 返回类型 静态成员函数名(参数表);
调用公有静态成员函数的一般格式:
a.类名::静态成员函数名(实参表);
b.对象.静态成员函数名(实参表);
c.对象指针->静态成员函数名(实参表);
静态成员函数的使用注意:
a.一般情况下,静态函数成员主要用来访问静态数据成员。当它与静态数据成员一起使用时,达到了对同一个类中对象之间共享的目的。
b.私有静态成员函数不能被类外部的函数和对象访问。
c.使用静态数据成员函数的一个原因是,可以用它在建立任何对象之前调用静态成员函数,以处理静态数据成员,这就是普通成员函数不能实现的功能
d.静态成员函数是类的一部分,而不是对象的一部分,格式:
类名::静态成员函数名();
如果已经定义了该类的对象,也可以通过对象调用。
e.非静态成员函数有this指针,而静态成员函数没有
**14.**友元
友元函数既可以是不属于任何类的非成员函数,也可以是另一个类的成员函数,在类中声明友元函数时,需在其函数名前加上关键字friend,友元函数可以定义在类的内部,也可以定义在类的外部
1.将非成员函数定义为友元函数
友元函数可以访问类的私有成员但它不是类的成员函数因此类外定义时不需要添加作用域运算符,而且不能通过this指针访问
在C++中,友元函数是对类的封装机制的补充,利用这种机制,一个类可以赋予某些函数访问它私有成员的特权。声明了一个类的友元函数,就可以用这个函数直接访问该类的私有数据,从而提高程序运行的效率。还有,当一个函数需要访问多个类的时候,友元函数非常有用,多个类可以使用一个友元函数,该友元函数可以访问相关的所有数据
2.将成员函数声明为友元函数
友元成员函数不仅可以访问自己所在类对象中的私有成员和公有成员,还可以访问friend声明语句所在类对象中的所有成员。一个类的成员函数作为另一个类的友元函数时,必须先定义这个类
Created by Administrator on 2021/11/18.//#include "iostream"#include "cstring"using namespace std;class Girl; //对Girl类的提前引用声明class Boy{public: static int number; Boy(); Boy(char *n,int d,int s){ name = new char[strlen(n)+1];//先把字符类型的name转换成为一个字符数组 strcpy(name,n);//字符数组和字符串类之间的相互转换 age = d; score +=s; number++; } void disp(Girl &); static void total_Disp();//类内声明静态成员函数private: static int score;//声明一个静态的数据成员,静态数据成员属于整个类,而不是类的某个对象 char *name; int age;};class Girl{public: Girl(char *n,int d){ name = new char[strlen(n)+1];//先把字符类型的name转换成为一个字符数组 strcpy(name,n);//字符数组和字符串类之间的相互转换 age = d; } friend void Boy::disp(Girl &); //声明类Boy的成员函数disp为类Girl的友元函数private: char *name; int age;};void Boy::disp(Girl &x) { cout<<"男孩的姓名 "<<name<<endl; cout<<"男孩的年龄 "<<age<<endl; cout<<""<<x.name<<endl; cout<<""<<x.age<<endl;}void Boy::total_Disp() { cout<<score<<endl;}int Boy::score=0;//静态数据成员的初始化int main(){ Boy::number= 100;//公有的静态数据成员可以在对象定义之前访问 Boy b1("张驰锋",19,1),b2,*b3; Girl g1("史晶晶",18); b3 = &b2; b1.disp(g1);//一个友元函数可以访问多个类的相关数据 //访问公有静态数据成员的方法 cout<<b1.number<<endl; cout<<b3->number<<endl; //调用静态成员函数的方法 Boy::total_Disp(); b1.total_Disp(); b3->total_Disp(); return 0;}
**13.**友元类
当类Y被说明为类X的友元时,类Y的所有成员函数都成为类X的友元函数,这就意味着作为友元类Y中的所有成员函数都可以访问类X中的所有成员(包括私有成员)
friend Boy;//声明类Boy是类Girl的友元类,则类Boy中的成员函数都是类Girl的友元函数,可以访问类Girl的所有成员
**14.**类的组合
也就是说在创建一个类的对象作为另一个类的成员变量
1.声明一个含有对象成员的类,首先要创建对象成员
2.在定义类Student的对象,调用构造函数进行初始化的同时,也需要对对象成员进行初始化:
Student::Student(char *name1,float s1):score1(s1){}
注意:在定义Student的构造函数时,必须缀上对象成员的名字,而不能缀上类名
Created by Administrator on 2021/11/21.//#include "iostream"#include "cstring"using namespace std;class A{public: A(int a,char *n){ name = new char[strlen(n)+1]; strcpy(name,n); }private: int age; char *name;};class B{public: B(int a,char *n,int s):a(a,n){ score =s ; }private: A a; int score;};
**15.**常类型
1.常引用,常引用做函数参数时,参数值不允许被改变
2.常对象,常对象的数据成员值在对象的整个生存周期内不允许改变,说明形式如下:类名 const 对象名[(参数表)] 或 const 类名 对象名[(参数表)]
3.常数据成员,常数据成员的值只能通过构造函数采用成员初始化列表来初始化,其它方法无法改变其值:
Date::Date(int y,int m,int h):year(y),month(m),hour(h){}
4.常成员函数,格式:类型说明符 函数名 (参数表)
常成员函数可以访问数据成员,也可以访问普通的数据成员。常数据成员可以被常成员函数访问,也可以被普通成员函数访问。
如果将一个对象说明为常对象,则通过该对象只能调用它的常成员函数,常成员函数是常对象唯一的对外接口
常成员函数不能更新对象的数据成员
Created by Administrator on 2021/11/21.//#include "iostream"#include "cstring"using namespace std;const int PI = 3;class A{public: A(int a,char *n,int s):score(s){//score只能通过初始化列表来初始化 name = new char[strlen(n)+1]; strcpy(name,n); } void disp() const {//声明常成员函数 cout<<age<<endl; } void eat(){ cout<<"I like eating"<<endl; }private: int age; char *name; const int score;//常数据成员score只能通过构造函数采用初始化列表来初始化};int main(){ int a=5; const int &b = a;//常引用,它所引用的对象不允许更改 const A a1(19,"张驰锋",100);//常对象a1的数据成员值在对象的整个生存周期内不运行改变 a1.eat();//这是错误的,常对象a1只能访问它的常成员函数 a1.disp();//正确,常对象只能调用常成员函数 return 0;}
Chapter4
**1.**派生类
声明格式:Class 派生类名:[继承方式] 基类名{
派生类新增的数据成员和成员函数
}
如果不显式地给出继承方式关键字,系统默认为私有继承(private)
**2.**基类成员在派生类中的访问属性
a.基类中的私有成员,无论哪种继承方式,基类中的私有成员不允许派生类继承,即在派生类中是不可直接访问的
b.基类中的公有成员,当类的继承方式为公有继承时,基类中所有的公有成员在派生类中仍以公有成员的身份出现;当类的继承方式为私有继承时,基类中的所有公有成员在派生类中都以私有成员的身份出现;当继承方式为保护继承时,基类中所有公有成员在派生类中都以保护成员的身份出现
c.基类中的保护成员,当类的继承方式为公有继承或者保护继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现;当继承方式为私有继承时,基类中的保护成员在派生类中以私有成员的身份出现
派生类对基类成员的访问形式:
内部访问:由派生类中新增的成员函数对基类继承来的成员的访问
外部访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问
**3.**私有继承的访问规则
基类中的成员 | 私有成员 | 公有成员 | 保护成员 |
---|
内部访问 | 不能 | 能 | 能 | 外部访问 | 不能 | 不能 | 不能 |
私有继承的方式下,外部成员都不能访问派生类中的成员,内部成员可以访问基类的公有成员和保护成员
**4.**公有继承的访问规则
基类中的成员 | 私有成员 | 公有成员 | 保护成员 |
---|
内部访问 | 不能 | 能 | 能 | 外部访问 | 不能 | 能 | 不能 |
**5.**保护继承的访问规则
基类中的成员 | 私有成员 | 公有成员 | 保护成员 |
---|
内部访问 | 不能 | 能 | 能 | 外部访问 | 不能 | 能 | 不能 |
无论是哪种继承,派生类都可以内部访问基类的公有成员和保护成员,派生类不能访问基类的私有成员,派生类外部访问只能访问基类的公有成员
**6.**派生类的构造函数
派生类不能继承基类的构造函数,当创建派生类的对象时,先执行基类的构造函数,再执行派生类的构造函数,派生类的构造函数创建格式:
函数名(参数表):基类名(参数表){}
a.可以使用类内声明,类外定义
b.若基类使用默认的构造函数或者不带参数的构造函数时,则在派生类中定义构造函数时可以略去“:基类名(参数表)”
c.当基类构造函数不带参数时,派生类不一定需要定义构造函数,但当基类的构造函数哪怕只含一个参数,它所有的派生类都必须定义构造函数
**7.**派生类的析构函数
派生类不能继承基类的析构函数,当撤销类的对象时,先执行派生类的析构函数,再执行基类的析构函数
8.含有对象成员的派生类的构造函数
格式:
派生类名(参数总表):基类名(参数表),对象成员1(参数表1),…,对象成员n(参数表n){}
执行顺序:
1.调用基类的构造函数,对基类数据成员进行初始化
2.调用内嵌对象成员的构造函数,对内嵌对象成员的数据成员初始化
3.执行派生类的构造函数体,对派生类数据成员初始化
撤销对象时,析构函数的调用顺序与构造函数相反
注意:
当派生类中含有多个内嵌对象成员时,调用内嵌对象成员的构造函数顺序由它们在类中声明的顺序确定
9.同名成员
如果在派生类中定义了与基类成员同名的成员,则称派生类的成员覆盖了基类的同名成员,在派生类中使用基类的同名成员时,需要加上基类名和作用域运算符。派生类的对象访问基类的同名成员时:
对象.基类名::同名成员();
10.多重继承
class 派生类名:继承方式1 基类名1,继承方式2 基类名2,…,继承方式n 基类名n{}
多重继承派生类的构造函数和析构函数
格式:
派生类名(参数总表):基类名1(参数表1),…,基类名n(参数表n){}
多重继承派生类在使用构造函数创建对象时,先执行基类的构造函数,再执行内嵌对象成员的构造函数,最后执行派生类的构造函数,析构函数的执行顺序刚好与其相反
Created by Administrator on 2021/11/21.//#include "iostream"#include "cstring"using namespace std;class A{public: A() {} A(int a,char *n,char *d){ name = new char[strlen(n)+1]; strcpy(name,n); department = new char[strlen(d)+1]; strcpy(department,d); } void disp(){ cout<<"dispA"<<endl; } void disp1(){ cout<<"disp1"<<endl; }private: int age; char *name;protected: char *department;};class B:private A{public: B() {} B(int a, char *n,char *d,int a1,char *n1,char *d1,int a2,char *n2,char *d2,int s) : A(a, n,d),a1(a1,n1,d1),a2(a2,n2,d2) { score = s; }//含有对象成员的派生类的构造函数 void disp(){ a1.A::disp();//在派生类中调用基类的同名成员 A::disp(); cout<<"dispB"<<endl; } void disp2(){ disp1(); cout<<"disp2"<<endl; }private: A a1,a2;//含有对象成员的派生类 int score;};class C:public A{public: C() {} C(int a, char *n, char *d,int p) : A(a, n, d) { phone = p; } void disp3(){ disp1(); cout<<"disp3"<<endl; }private: int phone;};class D:protected A{public: D() {} D(int a, char *n, char *d, int ad) : A(a, n, d) { address = ad; } void disp4(){ disp1(); cout<<"disp4"<<endl; }private: int address;};class E:public A,public B{//多重继承public: E(int a, char *n, char *d, int a1, char *n1, char *d1, int a11, char *n11, char *d11, int a2, char *n2, char *d2, int s, int ada) : A(a, n, d), B(a1, n1, d1, a11, n11, d11, a2, n2, d2, s){ adasd = ada; }//多重继承派生类的构造函数private: int adasd;};int main(){ A a; B b;//私有继承A C c;//公有继承A D d;//保护继承A b.disp2();//私有继承不能通过外部访问基类 c.disp1();//公有继承可以通过外部访问访问基类的公有成员 return 0;}
11.虚基类
1.为什么要引入虚基类
如果一个类中有多个直接基类,而这些基类又有一个共同基类,则在最底层的基类中会保留这个间接的共同基类数据成员的多份同名成员,引入虚基类可以消除二义性
2.虚基类的概念
格式: Class 派生类名 :virtual 继承方式 基类名{}
经过这样的声明后,当基类通过多条派生路径被一个派生类继承时,基类成员只保留一次
3.虚基类的初始化
1.如果在虚基类中定义有带形参的构造函数,并且没有定义默认形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化列表中列出对基类构造函数的调用,以初始化在虚基类中定义的数据成员
2.建立一个对象时,如果这个对象中含有虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其它基类对其的调用都自动忽略
3.若同一层次中同时包含虚基类和非虚基类,应先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数
4.若虚基类由非虚基类派生而来,则仍先调用基类的构造函数,再调用派生类的构造函数
说明:
1.关键字“virtual”与派生方式关键字(public或private)的先后顺序无关
2.一个基类在作为某些派生类虚基类的同时,也可以作为另一些派生类的非虚基类
Created by Administrator on 2021/11/21.//#include "iostream"#include "cstring"using namespace std;class Data_rec{public: Data_rec(string n,char s,int a):name(n),sex(s),age(a){}// friend ostream &operator<<(ostream &os, const Data_rec &rec) {// os << "name: " << rec.name << " sex: " << rec.sex << " age: " << rec.age;// return os;// }protected: string name; char sex; int age;};class Student:virtual public Data_rec{public: Student(const string &n, char s, int a, const string &major, double score) : Data_rec(n, s, a), major(major), score(score) {}protected: string major; double score;};class Employee:virtual public Data_rec{public: Employee(const string &n, char s, int a, const string &major, double salary) : Data_rec(n, s, a), major(major), salary(salary) {}protected: string major; double salary;};class E_Student:public Employee,public Student{public: E_Student(); E_Student(string n2, char s2, int a2, const string &n, char s, int a, const string &major, double salary, const string &n1, char s1, int a1, const string &major1, double score) : Data_rec(n2, s2, a2), Employee(n, s, a, major, salary), Student(n1, s1, a1, major1, score) {} void print();};void E_Student::print() { cout<<"name:"<<name<<endl;}int main(){ E_Student my_E_Student(); return 0;}
12.基类与派生类的赋值
1.派生类对象可以向基类对象赋值
Base b;//定义基类对象bDerived d;//定义派生类对象db=d;//用派生类对象给基类对象赋值
2.派生类对象可以初始化基类对象的引用
Base b;//定义基类对象bDerived d;//定义派生类对象dBase &br=d;//定义基类对象的引用,并用派生类的对象对其初始化//此时br和d拥有相同的存储单元
3.派生类对象的地址可以赋给指向基类对象的指针
Derived d;Base *br = &d;
4.如果函数的形参是基类对象或基类对象的引用,在调用函数时可以用派生类对象作为实参
注意:
1.指向基类对象的指针可以可以指向它的公有派生的对象,但不允许指向它的私有派生的对象
2.指向基类对象的指针可以可以指向它的公有派生的对象,但是指向派生类的指针不允许指向它的基类的对象
Created by Administrator on 2021/11/21.//#include "iostream"#include "cstring"using namespace std;class Data_rec{public: Data_rec(string n,char s,int a):name(n),sex(s),age(a){}// friend ostream &operator<<(ostream &os, const Data_rec &rec) {// os << "name: " << rec.name << " sex: " << rec.sex << " age: " << rec.age;// return os;// }protected: string name; char sex; int age;};class Student:virtual public Data_rec{public: Student(const string &n, char s, int a, const string &major, double score) : Data_rec(n, s, a), major(major), score(score) {}protected: string major; double score;};class Employee:virtual public Data_rec{public: Employee(const string &n, char s, int a, const string &major, double salary) : Data_rec(n, s, a), major(major), salary(salary) {}protected: string major; double salary;};class E_Student:public Employee,public Student{public: E_Student(); E_Student(string n2, char s2, int a2, const string &n, char s, int a, const string &major, double salary, const string &n1, char s1, int a1, const string &major1, double score) : Data_rec(n2, s2, a2), Employee(n, s, a, major, salary), Student(n1, s1, a1, major1, score) {} void print();};void E_Student::print() { cout<<"name:"<<name<<endl;}int main(){ E_Student my_E_Student(); return 0;}
Chapter5
1.运算符重载函数
1.不能重载的运算符
. | 成员访问运算符 |
---|
.* | 成员指针访问运算符 | :: | 作用域运算符 | Sizeof | 长度运算符 | ?: | 条件运算符 |
2.C++语言中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符
3.运算符重载是针对新类型数据的实际需要,对原有的运算符进行适当的改造完成的
4.重载不能改变运算符的操作对象的个数
5.重载不能改变运算符原有的优先级
6.重载不能改变运算符原有的结合特性
7.运算符重载函数的参数至少应有一个是类对象(或类对象的引用)
8.运算符重载函数可以是普通函数
2.友元运算符重载函数(形参要用引用类型)
1.类内定义格式:
friend 函数类型 operator 运算符 (形参表){}
2.类内声明,类外定义
class X{
? friend 函数类型 operator 运算符(形参表);
}
函数类型 operator 运算符(形参表){}
3.双目运算符重载
双目运算符有两个操作数,通常在运算符的左右两侧
eg. friend Complex operator+(Complex &x,Complex &y);
Complex operator+(Complex &x,Complex &y){}
调用方法: 在类X中采用友元函数重载运算符@,aa和bb是类X的两个对象
aa@bb;//隐式调用
operator@(aa,bb);//显式调用
隐式调用和显示调用是等价的
4.单目运算符重载
单目运算符只有一个操作数
eg. friend int operator+(int &x);
int operator+(int &x){}
调用方法: 在类X中采用友元函数重载运算符@,aa是类X的对象
@bb;//隐式调用
operator@(aa);//显式调用
隐式调用和显示调用是等价的
3.成员运算符重载函数
1.类内定义格式
函数类型 operator 运算符(形参表){}
2.类内写原型,类外定义
class X{
函数类型 operator 运算符(形参表);
}
函数类型 X::operator 运算符(形参表){}
3.双目运算符重载
对双目运算符而言,成员运算符重载函数的形参表中仅有一个参数,它作为运算符的右操作数,它的左操作数是隐含的,是该类的当前对象
eg.Complex operator+(Complex c);
Complex Complex::operator+(Complex c){}
调用方法:
aa@bb;//隐式调用
aa.operator+(bb);//显式调用
4.单目运算符重载
eg.Coord operator++();
Coord Coord::operator++(){}
调用方法:
@aa;//隐式调用
aa.operator@();//显式调用
4.友元与成员的比较
1.对双目运算符而言,成员运算符重载函数参数表中只有一个参数,而友元运算符重载函数参数表中含有两个参数;对单目运算符而言,成员运算符重载函数参数表中没有参数,而友元运算符重载函数参数表中有一个参数
2.双目运算符一般可以被重载为友元运算符重载函数或成员运算符重载函数,但如果是“整数+对象”,必须使用友元运算符重载函数
5.类型转换
1.隐式类型转换
在赋值表达式A=B的情况下,赋值运算符右端B的值需转换为A类型后进行赋值
当char或short类型变量与int类型变量进行运算时,将char或short类型转换成int类型
当两个操作对象类型不一致时,在算术运算前,级别低的类型自动转换为级别高的类型
2.显式类型转换
C语言中:(类型名)表达式
C++中:类型名(表达式)
6.类类型与系统预定义类型间的转换
1.转换构造函数
2.类型转换函数
格式:operator 目标类型(){}
7.虚函数
虚函数是重载的另一种表现形式,这是一种动态的重载,它提供了一种更为灵活的运行时的多态。虚函数允许调用与函数体之间的联系在运行时才建立,也就是在运行时才决定如何动作,即所谓的动态联编。
1.引入虚函数的前提
a.在基类和派生类中出现的同名的成员函数
b.存在继承关系
c.基类的指针指向派生类的地址
在c++中规定,基类的对象指针可以指向它的公有派生的对象,但是当其指向公有派生的对象时,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员,这样子无法实现动态调用,引入虚函数之后,可以实现动态调用
2.虚函数定义
格式:
virtual 函数类型 函数名(形参表){}
注意:
1.若在基类中,只声明虚函数原型(需要加virtual),而在类外定义时不需要加virtual
2.在派生类中,虚函数被重新定义时,其函数的原型与基类中的函数原型(即包括函数类型,函数名,参数个数,参数类型的顺序)都必须完全相同
3.当一个成员函数被定义为虚函数后,其派生类中符合重新定义虚函数要求的同名函数都自动成为虚函数
4.如果在派生类中没有对基类的虚函数重新定义,则公有派生类继承其直接基类的虚函数
5.虚函数必须时其所在类的成员函数,不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来觉得该激活哪个函数
6.虽然使用对象名和点运算符的方式也可以调用虚函数,但是这种调用时在编译时进行的,是静态联编,它没有利用虚函数的特性,只有通过基类指针访问虚函数时才能获得运行时的多态性,实现动态联编
3.虚析构函数
在C++中,不能声明虚构造函数,但是能声明虚析构函数
格式:virtual ~ 类名();
4.虚函数与函数重载
当普通的函数重载时,其函数的参数或参数类型必须有所不同,函数的返回类型可以不同。但是,当重载一个虚函数时,也就是说在派生类中重新定义虚函数时,要求函数名,返回类型,参数个数,参数的类型和顺序与基类中的虚函数原型完全相同
5.纯虚函数和抽象类
virtual 函数类型 函数名(参数表)= 0;
纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行重新定义。
注意:
1.由于抽象类中至少含有一个没有定义功能的纯虚函数,因此抽象类只能作用其它类的基类,不能建立抽象类对象
2.抽象类不能用作参数类型,函数返回类型或显式转换类型。但可以声明指向抽象类的指针变量,此指针可以指向它的派生类,进而实现多态性
3.如果在抽象类的派生类中没有重新说明纯虚函数,则该函数在派生类中仍然为纯虚函数,而这个派生类仍然还是一个抽象类
Chapter6
1.模板
C++允许用同一个函数名定义多个函数,这些函数的参数个数和参数类型不同。C++提供的模板是实现代码重用的一种工具,它可以实现类型参数化,即把类型定义为参数,从而真正实现了代码的可重用性。使用模板可以大幅度地提高程序设计的效率。模板分为函数模板和类模板,它们分别允许用户构造模板函数和模板类
2.函数模板
格式:
template <typename 类型参数>
返回类型 函数名(模板形参表){}
或是template <class 类型参数>
返回类型 函数名(模板形参表){}
声明和定义函数模板时,类型参数实际上是一个虚拟的类型名,但使用函数模板的时候,必须将类型参数实例化
注意:
1.在函数模板中允许使用多个类型参数,但是应注意template定义部分的每个类型参数前必须有关键字typename或class
2.在template语句与函数模板定义语句之间不允许有别的语句
3.模板函数类似于重载函数,只不过它更严格。函数重载的时候,每个函数体内可以执行不同的操作,但同一函数模板实例化后的所有模板函数都必须执行相同的操作
4.同一般函数一样,函数模板也可以重载
5.函数模板可以与同名的非模板函数重载。在这种情况下,调用的顺序是:首先寻找一个参数完全匹配的非模板函数,如果找到了就调用它;若没有找到,则寻找函数模板,将其实例化,产生一个匹配的模板函数,若找到了,就调用它
3.类模板与模板类
声明格式:
template <typename 类型参数>
class 类名{};
或是template <class 类型参数>
class 类名{};
用类模板定义对象时,采用以下形式: 类模板名 <实际类型名> 对象名 [(实参表列)]
类模板中的成员函数也可以采用类内声明,类外定义的形式。此时,若成员函数中有类型参数存在,则C++有一些规定:
1.需要在成员函数定义前进行模板声明
2.在成员函数名前缀上“类名<类型参数>::"
格式如下:
template <typename 类型参数>
函数类型 类名<类型参数>::成员函数名(形参表){}
注意: 1.在每个类模板定义之前,都需要在前面加上模板声明
2.模板类可以有多个类型参数
4.异常处理
程序中常见的错误分为两大类:编译时的错误和运行时的错误。编译时的错误比较容易修正,运行时的错误则不然。
在C++中处理异常主要由检查,抛出和捕获三个部分组成
**异常的抛出:**throw 表达式;
异常的检查和捕获:
try{
? 被检查的复合语句;
}
catch(异常类型声明1){
? 进行异常处理的语句1;
}
.
.
.
catch(异常类型声明n){
? 进行异常处理的语句n;
}
注意:
1.被检测的语句必须放在try块中,否则不起作用
2.try和catch块中必须有用花括号括起来的符合语句,即使花括号内只有一个语句也不能省略花括号
3.一个try_catch结构中只能有一个try块,但却可以有多个catch块,以便不同的异常信息匹配。catch后面的括号中,一般只写异常信息的类型名
4.如果在catch子句中没有指定异常信息的类型,而是用了三点删节号,则表示它可以捕获任何类型的异常信息
5.在某种情况下,在throw语句中可以不包括表达式
6.C++中,一旦抛出一个异常,而程序又不捕获的话,那么系统就会终止程序
|