/******************************************************************************************************************/express
1、C++面向对象编程_访问控制和继承编程
1.继承关系安全
class Person {函数
private:this
static int cnt; 指针
char *name; 对象
int age;blog
public:继承
static int getCount(void);内存
}
class Student : public Person {
//定义Student类,继承了Person
};
2.访问控制
private 外界不可见,不能直接访问
protected 外界不可见,不能直接访问;子类能够访问
public 外界能够直接访问
3.调整访问权限
派生类内部能够调整继承过来的成员(派生类可见的父类成员)的访问权限,能够提高或下降权限
使用using来实现,例:
class Father {
private:
int money;
protected:
int room_key;
}
class Son : public Father {
private:
int toy;
//using Father::it_skill;
public:
using Father::room_key;//使用using来调整权限为public
}
4.不一样继承方式(类型)
主要包括三种继承类型:public ,protected,private//公有继承,保护继承,私有继承
继承产生的权限结果见图
基类private成员派生类不可见(不可见 权限天然不会被改变)
公有继承,基类成员访问权限不变
保护继承,基类成员访问权限全变为protected
私有继承, 基类成员访问权限全变为private
1).不管哪一种继承方式,在派生类内部使用父类时并没有差异
2). 不一样的继承方式,会影响这两方面:
外部代码(类外)对派生类的使用(通常是建立对象使用)、
派生类的子类(派生类的子类怎么使用派生类里面的成员)
5.覆写
子类能够覆写从父类继承来的成员函数
6.派生类对象的空间分布
1).
class Student : public Person {
private:
int grade;
void setGrade(int grade) {this->grade = grade;}
int getGrade(void) {return grade;}
public:
void printInfo(void)
{
cout<<"Student ";
Person::printInfo();//调用父类的打印函数
}
};
2).派生类对象内存空间分部
派生类对象内存空间=父类内存空间+本身的独特的属性
//相似在基类内存空间后面追加一份属于派生类本身独特属性的空间
3).(向上)转型
void test_func(Person &p)
{
p.printInfo();
}
int main(int argc, char **argv)
{
Person p("lisi", 16);
Student s;
s.setName("zhangsan");
s.setAge(16);
test_func(p);
test_func(s); /* 等同于 Person &p = (s里面的Person部分);
p引用的是"s里面的Person部分",因此test_func里使用的是person的printInfo函数*/
s.printInfo();//
return 0;
}
派生类是基类的一种,因此 基类=派生类 时 等同于派生类里面的基类部分赋值给基类
/******************************************************************************************************************/
2、C++面向对象编程_多重继承
1.多重继承
class Sofabed : public Sofa, public Bed {
//不写public默认是私有继承
};
2.多个基类中有相同成员函数的情形
1).能够指定成员函数具体是属于哪一个基类的
s.Sofa::setWeight(100);
2).抽出相同的成员造成基类的父类
同时使用虚拟继承,来保证基类共用其父类的同一成员,从而保证派生类使用的是惟一成员(只使用了一个成员,派生类所占内存中只有该成员只占一份空间)
class Sofa : virtual public Furniture
{
};
class Bed : virtual public Furniture
{
};
class Sofabed : public Sofa, public Bed
{
};
int main(int argc, char **argv)
{
Sofabed s;
s.watchTV();
s.sleep();
s.setWeight(100);
return 0;
}
尽可能避免使用多重继承,这样会使得程序更加复杂,更容易出错
/******************************************************************************************************************/
3、C++面向对象编程_再论构造函数
1.构造顺序
先父后儿:
1)先调用基类的构造函数,
先虚拟继承的基类后通常继承的基类(相同类型的,先继承哪一个就先调用哪一个构造函数)
注意,虚拟继承的基类,构造函数只执行一次(虚拟基类中,只使用一分内存,因此只执行一次)
2)自身
先对象成员,后本身的
2.派生类调用基类的有参构造函数
在派生类的有参构造函数中使用:号加上类名(参数)
class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {
private:
Date date;
Type type;
public:
LeftRightSofabed()
{
cout <<"LeftRightSofabed()"<<endl;
}
LeftRightSofabed(char *str1, char *str2, char *str3) : Sofabed(str1), LeftRightCom(str2), date(str3)//派生类的有参构造函数中使用:号加上类名(参数)
{
cout <<"LeftRightSofabed()"<<endl;
}
};
3.类中对象的构造函数的调用
在派生类的有参构造函数中使用:号加上对象名(参数)
class LeftRightSofabed : public Sofabed, virtual public LeftRightCom {
private:
Date date;
Type type;
public:
LeftRightSofabed()
{
cout <<"LeftRightSofabed()"<<endl;
}
LeftRightSofabed(char *str1, char *str2, char *str3) : Sofabed(str1), LeftRightCom(str2), date(str3)
{//派生类的有参构造函数中使用:号加上对象名(参数)
cout <<"LeftRightSofabed()"<<endl;
}
};
/******************************************************************************************************************/
4、C++面向对象编程_多态
1.直接传入
没有转型,实现不了对应的状态,即没法实现多态
class Human
{
public:
void eating(void) { cout<<"use hand to eat"<<endl; }
};
class Englishman : public Human
{
public:
void eating(void) { cout<<"use knife to eat"<<endl; }
};
class Chinese : public Human
{
public:
void eating(void) { cout<<"use chopsticks to eat"<<endl; }
};
void test_eating(Human& h)
{
h.eating();
}
int main(int argc, char **argv)
{
Human h;
Englishman e;
Chinese c;
test_eating(h);
test_eating(e);
test_eating(c);//执行结果所有调用基类的函数,没实现多态
return 0;
}
2.引入虚函数
基类函数名加上virtual表示虚函数,基类对应的函数能够加也能够不加,同时派生类对应的函数能够加也能够不加,已是虚函数的属性了,此时在向上转型中派生类实现的与基类同样的函数就能够被调用(通常来讲也就是实现了覆写),不然在向上转型中调用的都是基类的(即没有加virtual的状况)。
所以,析构函数要加上virtual,加上后在向上转型中,即基类指针指向派生类的对象时(多态性),若是删除该转换后的基类指针;就会调用该指针指向的派生类析构函数(由于是虚函数),而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象彻底被释放。若是析构函数不被声明成虚函数,则编译器实施静态绑定,在删除转换后的基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会形成派生类对象析构不彻底。
注意,派生类的对象固然能够调用本身的成员函数不论是否有virtual。
向上转型:子对象转换为父对象(基类指针能够指向派生类的对象),转换时,若是代码中有执行基类没有的成员函数,则编译就会报错,因此是安全的。
class Human
{
private:
int a;
public:
virtual void eating(void)
{
cout<<"use hand to eat"<<endl;
}
};
class Englishman : public Human
{
public:
void eating(void) { cout<<"use knife to eat"<<endl; }
};
class Chinese : public Human
{
public:
void eating(void) { cout<<"use chopsticks to eat"<<endl; }
};
void test_eating(Human& h)
{
h.eating();
}
int main(int argc, char **argv)
{
Human h;
Englishman e;
Chinese c;
test_eating(h);
test_eating(e);//执行结果调用派生类的函数,实现多态
test_eating(c);//执行结果调用派生类的函数,实现多态
return 0;
}
内部实现机制
静态联编:非虚函数,在编译时就肯定了调用哪个
动态联编:
1).类里面有虚函数,则其对象里有指针,指向虚函数表,子类对象里因为继承也有这个指针,指向虚函数表
2).调用函数的时候,找到指针指向的虚函数表,而后调用里面的虚函数
因此使用虚函数时,对象占用的空间会变大
见下图:
3.虚函数注意事项
1).函数参数使用对象的指针或者引用,才有多态
传值时,无多态(强制转换为基类类型,只剩下基类部分,就没有指针了,静态联编,调用的就只能是基类了)
2).只有类的成员函数才能声明为虚函数
3).静态成员不能是虚函数
4).内联函数不能是虚函数
5).构造函数不能是虚函数
6).析构函数通常都声明为虚函数
这样才能够先释放派生类的(调用本身的清理函数),再调用基类的析构(清理)函数,而不是只释放基类的
class Human
{
private:
int a;
public:
virtual void eating(void) { cout<<"use hand to eat"<<endl; }
virtual ~Human() { cout<<"~Human()"<<endl; }
};
class Englishman : public Human
{
public:
void eating(void) { cout<<"use knife to eat"<<endl; }
virtual ~Englishman() { cout<<"~Englishman()"<<endl; }
};
class Chinese : public Human
{
public:
void eating(void) { cout<<"use chopsticks to eat"<<endl; }
virtual ~Chinese() { cout<<"~Chinese()"<<endl; }
};
7).重载函数(函数参数不一样),不能够设为虚函数
重载已是多态了,因此不能够设置为虚函数了。
或者能够理解为,多态是相同的调用方法,能够调用到不一样类里面实现的函数,而重载函数参数不一样,已经不是相同的调用方法了,因此不能也须要设为虚函数
8).覆写(覆盖)函数(函数参数,返回值都相同),能够设置为虚函数
9).函数参数都相同,返回值不一样时,不能够设置为虚函数,有个例外:
当返回值是本类指针或引用时,能够设置为虚函数
/******************************************************************************************************************/
5、C++面向对象编程_类型转换
1.隐式类型转换:
double d = 100.1;
int i = d; // double转为int
char *str = "123";
int *p = str; // char *转为int *
2.显式类型转换:
兼容c的类型转换,同时具备新的转换特性,
以下的各类转换语句的意思都是把expression转换成type-id类型的对象,
1).reinterpret_cast<type_id>(expression)//从新解析转换 强制类型转换
//是模版函数
//至关于c风格的用小括号()实现的强制类型转换
int *p = reinterpret_cast<int *>(str2); // char *转换为int * ,至关于C风格的()
没法转换const或volatile属性的变量(不能转换只读的为可读可写),否则会编译报错
2).const_cast<type_id>(expression)//模版函数
用来去除原来类型的const或volatile属性
char *str2 = const_cast<char *>(str); //转换const型变量使用到
int *p = reinterpret_cast<int *>(str2); // char *转换为int * ,至关于C风格的()
3).dynamic_cast<type_id>(expression)//动态类型转换
该运算符把expression转换成type-id类型的对象。
I、Type-id必须是类的指针、类的引用或者void *;
若是type-id是类指针类型,那么expression也必须是一个指针;
若是type-id是一个引用,那么expression也必须是一个引用。
例子:
void test_eating(Human& h)
{//Human& h传进来的由程序来决定的,不能事先肯定,这个肯定过程是动态的,因此称为动态转换
Englishman *pe;
Chinese *pc;
h.eating();
/* 想分辨这个"人"是英国人仍是中国人? */
if (pe = dynamic_cast<Englishman *>(&h))//指针的转换
/*根据指针找到虚函数表找到类信息从而知道对象是否属于某一个类(虚函数表中有类信息以及继承信息)
cout<<"This human is Englishman"<<endl;
if (pc = dynamic_cast<Chinese *>(&h))
cout<<"This human is Chinese"<<endl;
/*根据指针找到虚函数表找到类信息从而知道对象是否属于某一个类,同时虚函数表里面除了类信息还有继承信息,因此也能知道类属于哪一个父类*/
}
因此动态类型转换只能用在含有虚函数的类里面,即用于多态的场合
II、动态转换能够转换指针也能够转换引用
若是一个引用不能指向一个实体就没有存在的必要了,引用也不能用来做判断,因此会致使程序崩溃,
因此动态转换常用指针而不是引用
III、主要用于类层次间的上行转换(派生类对象转为基类对象)和下行转换(基类对象转为派生类对象),还能够用于类之间的交叉转换
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是同样的;
在进行下行转换时,dynamic_cast具备类型检查的功能,比static_cast更安全
进行上下行转换是经过虚函数表的类继承信息来判断,转换有可能成功或失败
4).static_cast<type_id>(expression)
//转换在编译时由编译器决定的,转换是事先肯定的,因此是静态转换,因此不能转换时编译器会报错
Expression //待转换的对象
type_id //转换后的类型
返回转换后的变量
该运算符把expression转换为type-id类型,
但运行时没有类型检查来保证转换的安全性。
例子:
Human h;
//Englishman e;
//Chinese c;
Guangximan g;
Englishman *pe;
pe = static_cast<Englishman *>(&h);//能够转换但不安全
//Englishman *pe2 = static_cast<Englishman *>(&g);//不能转换
Chinese *pc = static_cast<Chinese *>(&g);//能够转换
使用场景:
I、用于类层次结构中基类和子类之间指针或引用的转换。
II、进行上行转换(把子类的指针或引用转换成基类表示)是安全的(转换时,若是代码中有执行基类没有的成员函数,则编译就会报错,因此是安全的);
III、进行下行转换(把基类指针或引用转换成子类指针或引用)时,因为没有动态类型检查,因此是不安全的。
IV、用于基本数据类型之间的转换,如把int转换成char,把int转换成enum:这种转换的安全性也要开发人员来保证。
V、把void指针转换成目标类型的指针(不安全!!)
VI、把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。