面对对象编程进阶(1)
- 1.初始化列表
- 2.类的继承
- 3.深挖公有、私有及保护
- 4.友元类
- 5.类指针
1.初始化列表
C++中类的初始化列表应用于构造函数初始化类的成员变量。其具体应用方法为:
class Student
{public:int age;string name;Student():age(0),name("ZhangSan"){}Student(int a,string b):age(a),name(b){}
};
int main()
{Student ZhangSan,LiSi(20,"LiSi");cout<<ZhangSan.name<<" "<<ZhangSan.age<<endl;cout<<LiSi.name<<" "<<LiSi.age<<endl;
}
// 输出为:ZhangSan 0
// LiSi 20
初始化列表赋值的方法可以在创建构造函数时进行,并且这种方法也可以为初始化私有成员。初始化列表可以提高代码的执行效率和可读性,在继承问题中,我们还会继续探讨初始化列表。
2.类的继承
通俗地讲,如果有两个类A和B,假如B类可以获得A类的部分或全部的属性和方法,我们就可以称B继承了A,即B是A的派生类。除了可以从A类中获得非私有成员外,B还可以有自己独特的属性和方法(这一点和python相同),包括自己的构造函数。但是对于B类来说,初始化列表就只能给自己声明的属性赋值,不能用于给继承来的属性赋值了。下面我们看一个简单的例子:
class Student
{public:int age;string name;Student():name("ZhangSan"),age(0){}Student(int a,string b):name(b),age(a){}
};
class undergraduate:public Student
{public:string research;undergraduate():research("computer"){} // 尝试仅初始化research属性,最终不能成功undergraduate(int a,string b):research("computer") // 继承父类重构的构造函数{// name和age作为继承来的属性无法使用初始化列表赋值name=b; age=a;}
};
int main()
{undergraduate LiSi(24,"LiSi");cout<<LiSi.age<<LiSi.name<<LiSi.research<<endl;undergraduate ZhangSan;cout<<ZhangSan.age<<ZhangSan.name<<ZhangSan.research<<endl;Student WangWu(19,"WangWu");cout<<WangWu.age<<WangWu.name<<endl;
}
// 输出为:24LiSicomputer
// 20ZhangSancomputer
// 19WangWu
这段代码中,undergraduate类是Student类的派生,当我们实例化undergraduate类时,是一定会先调用基类Student的无参数构造函数的,对于LiSi实例,创建初会先在Student类的无参数构造函数中被赋值,name为ZhangSan,age为0,然后再重返undergraduate类的含参数构造函数中初始化research为computer,并修改name和age两个属性。对于ZhangSan实例,创建时首先也是会在Student类的无参数构造函数中赋初值,然后才在undergraduate类的无参数构造函数中初始化research。
这段代码值得我们仔细分析,建议大家从main函数的第一句打断点,一句一句debug。
理解了上述的代码,我们来分析一个问题,我们实例化LiSi时,会自动跳转到基类的无参数构造函数中赋初值,这个初值不是我们想要的,所以后面还要再花时间去改写,这造成了时间上的浪费。那么有没有什么办法闭麦那这个过程呢?
想解决这个问题,我们首先需要知道,undergraduate构造函数会跳转到Student中是因为代码默认派生类的构造函数继承基类的无参数构造函数。我们可以让undergraduate类的含参构造函数继承Student的含参构造函数,然后再给undergraduate类的新建成员赋初值就可以了:
class Student
{public:int age;string name;Student():name("ZhangSan"),age(0){}Student(int a,string b):name(b),age(a){}
};
class undergraduate:public Student
{public:string research;undergraduate():research("computer"){} undergraduate(int a,string b):Student(a,b),research("computer"){} // 继承父类含参构造函数
};
int main()
{undergraduate LiSi(24,"LiSi");cout<<LiSi.age<<LiSi.name<<LiSi.research<<endl;
}
其实我们也可以写出让undergraduate类的无参数构造函数继承 Student类的无参数构造函数,但是这个继承是默认的,因此我就省略了。这个例子告诉我们,无论我们是否需要,使用派生类时也都必须要调用基类的构造函数,但是带参数的构造函数是不会被自动继承到派生类中的。如果派生类需要使用带参数的构造函数,应当声明继承关系。
3.深挖公有、私有及保护
上一节我们没有提到继承的概念,因此就简单的把公有和私有成员的区别说成了是否可以在类外直接使用。然而有了继承关系我们就需要把类内和类外分的更细致。公有成员可以在任何位置直接使用;私有成员只能在基类内(或友元类内,下面会讲到)直接使用,派生类也不可以直接使用;受保护成员可以在基类、友元类和派生类内直接使用。下面我们看一个例子:
class Student
{public:int age=0;void show(){cout<<age<<name<<uni<<endl; //在基类内可以访问私有和被保护成员}private:string uni="S university";protected:string name="no";
};
class undergraduate:public Student
{public:void show_amand(){// uni="a university"; // 无法访问私有成员name="ZhangSan";age=20;cout<<age<<name<<endl;}
};
int main()
{Student ZhangSan;ZhangSan.show(); // 借由公有方法间接查看私有和被保护成员// ZhangSan.uni; // 无法访问私有成员// ZhangSan.name; // 无法访问受保护成员cout<<ZhangSan.age<<endl;undergraduate t;t.show_amand();
}
受保护和私有的方法同属性成员所受限制一样,感兴趣的小伙伴可以自己写尝试一下。
4.友元类
上一节我给大家留了个扣子,现在我们来看看友元类的情况。友元类本质上也是一个类,只不过它的权限比基类只高不低。友元类的使用必须慎之又慎,因为滥用友元类可能会危害封装性。
在A类中使用friend关键字可以声明B类是A类的友元类,友元类可以直接操作任何基类的成员,不会受到限制,并且还可以独立拥有自己的成员:
class Student
{public:string name="ZhangSan";private: // 直接以使用限制最大的私有类型为例char sex='w';friend class Teacher;
};
class Teacher // 定义友元类
{public:void access_sex(Student& aa) // 函数这样使用形参可以引用对应实例的私有内容// 可以理解成在友元类中把传递进来的实例改名{aa.sex = 'm'; // 可以访问私有成员aa.name="LiSi";cout<<aa.sex<<aa.name<<endl;}
};
int main()
{Student Zhangsan;Teacher LiSi;LiSi.access_sex(Zhangsan); // 借由友元类LiSi修改Zhansan的私有属性sexcout<<Zhangsan.name<<endl; // 通过打印公有内容看看友元类是否真的成功修改成员
}
// 结果为:mLiSi
// LiSi
很惊讶,我们确实可以通过友元类改掉基类的私有属性,换成是受保护类或者私有方法等结果也是一样的。
5.类指针
类指针和普通的指针类型在声明上并无区别,且其功能也是指向对应实例。但由于继承关系的存在,类指针存在这样的设定,即基类指针除了指向基类实例外,还可以指向子类实例,但是子类指针只能指向子类实例不可以指向基类实例的。附:基类的友元类指针既可以指向友元类实例,也可以指向基类实例,还可以指向子类实例。下面我们还是举一个简单的例子:
class Student
{public:int age=0;string name=" ZhangSan ",uni=" S university ";void show(){cout<<uni<<name<<age<<endl;}
};
class undergraduate:public Student
{public:undergraduate(){name=" LiSi ";}string research="computer";
};
int main()
{// 首先不考虑继承Student* p;Student ZhangSan;p=& ZhangSan;cout<<p->name<<endl;p->show();// 考虑继承undergraduate* p1;undergraduate LiSi;p=&LiSi; // 父类指针指向子类实例p1=&LiSi; // 子类指针指向子类实例// p1=&ZhangSan; // 报错p->show();cout<<p->name<<endl;cout<<p->uni<<endl;// cout<<p->research<<endl; // 虽然父类指针指向了子类实例,但依然不能调用子类特有的属性和方法cout<<p1->name<<p1->research<<endl;
}
需要重点注意的只有一点,那就是虽然基类指针可以指向子类实例,但不可以调用子类的特有成员。
为了方便大家消化,这次就先讲到这里吧,剩下的内容我会放在下一节讲。