int *ptr;
ptr++;
(*ptr)++
*ptr++;
int a = 5;
int &b =a;
b++;
1.继承
基类中,在类内,public,protected, private都是可以随意访问的;在基类的对象中,只能访问public成员;
public继承: 基类的public成员,在派生类中依旧是public属性,派生类的对象可访问以访问;
基类中的private成员,在派生类中是private属性,派生类中的对象不可以访问;派生类中也不可以访问;
基类中的protected成员,在派生类中是protected属性,派生类的对象中不可以访问,派生类中是可以访问基类的protected成员的;
private继承:基类中的public成员,在派生类中是private属性;在派生类对象中是不可以访问;基类中public变成派生类中private, 在派生类中是可以访问的;
基类中private成员,在派生类中是private属性,在派生类和派生对象中都是不可以访问的;
基类中protected成员,在派生类中是private属性,在派生类中是可以访问的,在派生类的对象中是不可以访问的;
protected继承: 基类中public成员,在派生类中是protected属性,在派生类中是可以直接访问,在派生类对象中无法访问;
。。。protected 成员,在派生类中protected属性,在派生类中可直接访问,在派生类对象中不能访问;
基类中private成员,在派生类中private属性,在派生类中无法访问,派生类对象无法访问;
using 改变权限:把public可以改成保护的,私有的;
protected可以改成public,私有的;
private 不能改;
2。名字遮蔽
在继承中不存在重载,发生遮蔽;派生类函数名和基类函数名相同,不论参数如何,名字相同就发生遮蔽;
3. 继承的时候内存模型
派生类中的内存是:基类中变量所占空间+ 派生类自己的变量空间;
成员函数仍然在代码区, 共享;
4。派生类构造函数
派生类构造函数调用基类的构造函数来实现,不能放在函数体中调用,因为基类的构造函数不能被继承;
派生类构造函数调用的基类的构造函数,参数是实参,不是形参;
5。派生类的析构函数
构造函数,析构函数都是不能被继承的;
A-〉B-〉C的派生顺序,构造函数顺序:A-〉B-〉C ,析构顺序C-〉B-〉A;
6。多继承
类A,B,C,
class D:public A,public B,public C{};
基类构造函数出现的顺序和继承时的顺序有关;
7。命名冲突,虚继承
class B:virtual public A{ };
虚继承是表示派生类愿意共享他的基类;
被共享的基类,叫做虚基类;
8。多继承的构造函数(虚继承)
构造函数是受间接基类影响,不是直接基类;
A----B,C -- D
B,C是D的直接基类;
A是D的间接基类;
细节:
一.引用
引用是变量的一个别名,通过这个别名和原来的名字都可以找到这份数据。
一个人的名字和绰号,表示同一个人。
1。引用和变量
<类型> &name = data;
引用必须在定义的时候同时初始化,后面不能再引用其他数据,类似于const
;
#include <iostream>
using namespace std;
int main()
{
int a = 99;
int &b = a;
cout<<a<<","<<b<<endl;
cout<<&a<<","<<&b<<endl;
return 0;
}
引用在定义的时候加&,使用的时候不用,加&表示地址。
不希望引用修改数据,就在前面加const
;
const <类型> &name = data;或
<类型> const &name = data;
2。引用作为函数参数
用引用作为形参,调用时形参和实参就绑定在一起,指向同一份数据,如同C中的地址传递。
修改形参,实参也改变,不会出现C中数值传递的时候实参不改变。
#include <iostream>
using namespace std;
void swap1(int a, int b);
void swap2(int *p1, int *p2);
void swap3(int &a, int &b);
int main(){
int num1, num2;
cout<<"Input two integers: ";
cin>>num1>>num2;
swap1(num1, num2);
cout<<num1<<" "<<num2<<endl;
cout<<"Input two integers: ";
cin>>num1>>num2;
swap2(&num1, &num2);
cout<<num1<<" "<<num2<<endl;
cout<<"Input two integers: ";
cin>>num1>>num2;
swap3(num1, num2);
cout<<num1<<" "<<num2<<endl;
return 0;
}
//直接传递参数内容
void swap1(int a, int b){
int temp = a;
a = b;
b = temp;
}
//传递指针
void swap2(int *p1, int *p2){
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
//按引用传参 a,b分别绑定num1,num2了
void swap3(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
3。引用作为函数返回值
#include <iostream>
using namespace std;
int &sum(int &num)
{
num = num +1;
return num;
}
int main()
{
int n = 20;
int m = sum(n);
cout<<m<<endl;
return 0;
}
注意:不能返回局部变量的引用;
如:
#include <iostream>
using namespace std;
int &plus10(int &n){
int m = n + 10;
return m; //返回局部数据的引用
}
int main(){
int num1 = 10;
int num2 = plus10(num1);
cout<<num2<<endl;
int &num3 = plus10(num1);
int &num4 = plus10(num3);
cout<<num3<<" "<<num4<<endl;
return 0;
}
4。引用的本质,和指针的区别
引用只是对指针进行了封装,底层还是通过指针实现的。
4.1 引用必须在定义时初始化,从一而终,不能指向其他数据;
指针没这个限制,不一定赋值,也能指向任意数据。
4.2 可以有const指针,但是没有const引用;
int a = 20;
int & const r = a; error
const int *const ptr ;
多此一举
3. 指针可以有多级:int **p;但引用int &&p是错误的;
可以如下:
int a = 10;
int &r = a;
int &rr = r;
4. 指针和引用的自增++,自减--表示不一样。
指针表示指向的地址发生改变,指向下一个单元;
引用表示指向的值改变;
char a='c' ; --- &a = 0x10000000;
char *ptr=&a;
ptr++;
ptr--;
0x100000000;
int a = 3;
int &b= a;
b++;
b--;
#include <iostream>
using namespace std;
int main (){
int a = 10;
int &r = a;
r++;
cout<<r<<endl;
int arr[2] = { 27, 84 };
int *p = arr;
p++;
cout<<*p<<endl;
return 0;
}
二.继承和派生
一个人的特征,在血型,肤色,身材,相貌方面有父母的特征,同时和兄弟姐妹有区别的特征。
2.1 概念
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。
例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。
被继承的类称为父类或基类,继承的类称为子类或派生类。
定义新类的时候可以从一个或多个既有类中继承所有的数据成员和函数成员,然后再加上自己的
新成员或重新定义由继承得到的成员。
继承格式:
class 派生类名:[继承方式] 基类名{
派生类新增加的成员
};
#include<iostream>
using namespace std;
//基类 Pelple
class People{
public:
void setname(char *name);
void setage(int age);
char *getname();
int getage();
private:
char *m_name;
int m_age;
};
void People::setname(char *name){ m_name = name; }
void People::setage(int age){ m_age = age; }
char* People::getname(){ return m_name; }
int People::getage(){ return m_age;}
//派生类 Student
class Student: public People{
public:
void setscore(float score);
float getscore();
private:
float m_score;
};
void Student::setscore(float score){ m_score = score; }
float Student::getscore(){ return m_score; }
int main(){
Student stu;
stu.setname("小明");
stu.setage(16);
stu.setscore(95.5f);
cout<<stu.getname()<<"的年龄是 "<<stu.getage()<<",成绩是 "<<stu.getscore()<<endl;
return 0;
}
class Student: public People
这就是声明派生类的语法。class 后面的“Student”是新声明的派生类,冒号后面的“People”是已经存在的基类。
在“People”之前有一关键宇 public,用来表示是公有继承。
2.2 继承的权限
C++继承的一般语法为:
class 派生类名:[继承方式] 基类名{
派生类新增加的成员
};
继承方式限定了基类成员在派生类中的访问权限,包括 public(公有的)、private(私有的)和 protected(受保护的)。
此项是可选项,如果不写,默认为 private(成员变量和成员函数默认也是 private)。
类成员的访问权限由高到低依次为 public --> protected --> private,
public 和 private:public 成员可以通过对象来访问,private 成员不能通过对象访问
protected 成员和 private 成员类似,也不能通过对象访问。
但是当存在继承关系时,protected 和 private 就不一样了:
基类中的 protected 成员可以在派生类中使用,而基类中的 private 成员不能在派生类中使用。
public、protected、private 指定继承方式
不同的继承方式会影响基类成员在派生类中的访问权限。
1) public继承方式
基类中所有 public 成员在派生类中为 public 属性;
基类中所有 protected 成员在派生类中为 protected 属性;
基类中所有 private 成员在派生类中不能使用。
2) protected继承方式
基类中的所有 public 成员在派生类中为 protected 属性;
基类中的所有 protected 成员在派生类中为 protected 属性;
基类中的所有 private 成员在派生类中不能使用。
3) private继承方式
基类中的所有 public 成员在派生类中均为 private 属性;
基类中的所有 protected 成员在派生类中均为 private 属性;
基类中的所有 private 成员在派生类中不能使用。
基类中:
public成员:在基类,派生类的类内,和对象都可以访问;
private成员:只能是当前类的类内访问,对象和派生类的类内和对象都不可以访问;
public成员: 基类和派生类的对象都不可以访问,但是基类和派生类类内可以访问;
1。 继承方式中的 public、protected、private 是用来指明基类成员在派生类中的最高访问权限的。
2) 不管继承方式如何,基类中的 private 成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)
3) 如果希望基类的成员能够被派生类继承并且毫无障碍地使用,那么这些成员只能声明为 public 或 protected;
只有那些不希望在派生类中使用的成员才声明为 private。
4) 如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为 protected。
基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,
导致无法使用罢了。private 成员的这种特性,能够很好的对派生类隐藏基类的实现,以体现面向对象的封装性。
#include<iostream>
using namespace std;
//基类People
class People{
public:
void setname(char *name);
void setage(int age);
void sethobby(char *hobby);
char *gethobby();
protected:
char *m_name;
int m_age;
private:
char *m_hobby;
};
void People::setname(char *name){ m_name = name; }
void People::setage(int age){ m_age = age; }
void People::sethobby(char *hobby){ m_hobby = hobby; }
char *People::gethobby(){ return m_hobby; }
//派生类Student
class Student: public People{
public:
void setscore(float score);
protected:
float m_score;
};
void Student::setscore(float score){ m_score = score; }
//派生类Pupil
class Pupil: public Student{
public:
void setranking(int ranking);
void display();
private:
int m_ranking;
};
void Pupil::setranking(int ranking){ m_ranking = ranking; }
void Pupil::display(){
cout<<m_name<<"的年龄是"<<m_age<<",考试成绩为"<<m_score<<"分,班级排名第"<<m_ranking<<",TA喜欢"<<gethobby()<<"。"<<endl;
}
int main(){
Pupil pup;
pup.setname("小明");
pup.setage(15);
pup.setscore(92.5f);
pup.setranking(4);
pup.sethobby("乒乓球");
pup.display();
return 0;
}
在派生类中访问基类 private 成员的唯一方法就是借助基类的非 private 成员函数,如果基类没有非 private 成员函数,
那么该成员在派生类中将无法访问(除非使用下面讲到的 using 关键字)
2.3 using改变权限
使用 using 关键字可以改变基类成员在派生类中的访问权限,例如将 public 改为 private、将 protected 改为 public。
#include<iostream>
using namespace std;
//基类People
class People{
public:
void show();
protected:
char *m_name;
int m_age;
};
void People::show(){
cout<<m_name<<"的年龄是"<<m_age<<endl;
}
//派生类Student
class Student: public People{
public:
void learning();
public:
using People::m_name; //将procted改为public
using People::m_age; //将procted改为public
float m_score;
private:
using People::show; //将public改为private
};
void Student::learning(){
cout<<"我是"<<m_name<<",今年"<<m_age<<"岁,这次考了"<<m_score<<"分!"<<endl;
}
int main(){
Student stu;
stu.m_name = "小明";
stu.m_age = 16;
stu.m_score = 99.5f;
stu.show(); //compile error
stu.learning();
return 0;
}
2.4 遮蔽
如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。
所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,
实际上使用的是派生类新增的成员,而不是从基类继承来的。
#include<iostream>
using namespace std;
//基类People
class People{
public:
void show();
protected:
char *m_name;
int m_age;
};
void People::show(){
cout<<"嗨,大家好,我叫"<<m_name<<",今年"<<m_age<<"岁"<<endl;
}
//派生类Student
class Student: public People{
public:
Student(char *name, int age, float score);
public:
void show(); //遮蔽基类的show()
private:
float m_score;
};
Student::Student(char *name, int age, float score){
m_name = name;
m_age = age;
m_score = score;
}
void Student::show(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}
int main(){
Student stu("小明", 16, 90.5);
//使用的是派生类新增的成员函数,而不是从基类继承的
stu.show();
//使用的是从基类继承来的成员函数
stu.People::show();
return 0;
}
2.5 基类和派生类遮蔽不构成重载
基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,
那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样。
#include<iostream>
using namespace std;
//基类Base
class Base{
public:
void func();
void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }
//派生类Derived
class Derived: public Base{
public:
void func(char *);
void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }
int main(){
Derived d;
d.func("c.biancheng.net");
d.func(true);
d.func(); //compile error
d.func(10); //compile error
d.Base::func();
d.Base::func(100);
return 0;
}
2.6 派生类构造函数
在派生类的构造函数中调用基类的构造函数;
#include<iostream>
using namespace std;
//基类People
class People{
protected:
char *m_name;
int m_age;
public:
People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}
//派生类Student
class Student: public People{
private:
float m_score;
public:
Student(char *name, int age, float score);
void display();
};
//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}
int main(){
Student stu("小明", 16, 90.5);
stu.display();
return 0;
}
构造函数调用顺序:先基类再派生类
****派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的
2.7基类构造函数调用规则
事实上,通过派生类创建对象时必须要调用基类的构造函数,这是语法规定。
换句话说,定义派生类构造函数时最好指明基类构造函数;如果不指明,
就调用基类的默认构造函数(不带参数的构造函数);如果没有默认构造函数,
那么编译失败。请看下面的例子:
#include <iostream>
using namespace std;
//基类People
class People{
public:
People(); //基类默认构造函数
People(char *name, int age);
protected:
char *m_name;
int m_age;
};
People::People(): m_name("xxx"), m_age(0){ }
People::People(char *name, int age): m_name(name), m_age(age){}
//派生类Student
class Student: public People{
public:
Student();
Student(char*, int, float);
public:
void display();
private:
float m_score;
};
Student::Student(): m_score(0.0){ } //派生类默认构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}
int main(){
Student stu1;
stu1.display();
Student stu2("小明", 16, 90.5);
stu2.display();
return 0;
}
3。 派生类的析构函数
和构造函数类似,析构函数也不能被继承。
另外析构函数的执行顺序和构造函数的执行顺序也刚好相反:
创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。
而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。
#include <iostream>
using namespace std;
class A{
public:
A(){cout<<"A constructor"<<endl;}
~A(){cout<<"A destructor"<<endl;}
};
class B: public A{
public:
B(){cout<<"B constructor"<<endl;}
~B(){cout<<"B destructor"<<endl;}
};
class C: public B{
public:
C(){cout<<"C constructor"<<endl;}
~C(){cout<<"C destructor"<<endl;}
};
int main(){
C test;
return 0;
}
4。多继承
例如已声明了类A、类B和类C,那么可以这样来声明派生类D:
class D: public A, private B, protected C{
//类D新增加的成员
}
多继承下的构造函数
多继承形式下的构造函数和单继承形式基本相同,只是要在派生类的构造函数中调用多个基类的构造函数。
以上面的 A、B、C、D 类为例,D 类构造函数的写法为:
D(形参列表): A(实参列表), B(实参列表), C(实参列表){
//其他操作
}
基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。
仍然以上面的 A、B、C、D 类为例,即使将 D 类构造函数写作下面的形式:
D(形参列表): B(实参列表), C(实参列表), A(实参列表){
//其他操作
}
那么也是先调用 A 类的构造函数,再调用 B 类构造函数,最后调用 C 类构造函数。
#include <iostream>
using namespace std;
//基类
class BaseA{
public:
BaseA(int a, int b);
~BaseA();
protected:
int m_a;
int m_b;
};
BaseA::BaseA(int a, int b): m_a(a), m_b(b){
cout<<"BaseA constructor"<<endl;
}
BaseA::~BaseA(){
cout<<"BaseA destructor"<<endl;
}
//基类
class BaseB{
public:
BaseB(int c, int d);
~BaseB();
protected:
int m_c;
int m_d;
};
BaseB::BaseB(int c, int d): m_c(c), m_d(d){
cout<<"BaseB constructor"<<endl;
}
BaseB::~BaseB(){
cout<<"BaseB destructor"<<endl;
}
//派生类
class Derived: public BaseA, public BaseB{
public:
Derived(int a, int b, int c, int d, int e);
~Derived();
public:
void show();
private:
int m_e;
};
Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){
cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
cout<<"Derived destructor"<<endl;
}
void Derived::show(){
cout<<m_a<<", "<<m_b<<", "<<m_c<<", "<<m_d<<", "<<m_e<<endl;
}
int main(){
Derived obj(1, 2, 3, 4, 5);
obj.show();
return 0;
}
5。命名冲突
当两个或多个基类中有同名的成员时,如果直接访问该成员,就会产生命名冲突,
编译器不知道使用哪个基类的成员。这个时候需要在成员名字前面加上类名和域解析符::,
以显式地指明到底使用哪个类的成员,消除二义性。
#include <iostream>
using namespace std;
//基类
class BaseA{
public:
BaseA(int a, int b);
~BaseA();
public:
void show();
protected:
int m_a;
int m_b;
};
BaseA::BaseA(int a, int b): m_a(a), m_b(b){
cout<<"BaseA constructor"<<endl;
}
BaseA::~BaseA(){
cout<<"BaseA destructor"<<endl;
}
void BaseA::show(){
cout<<"m_a = "<<m_a<<endl;
cout<<"m_b = "<<m_b<<endl;
}
//基类
class BaseB{
public:
BaseB(int c, int d);
~BaseB();
void show();
protected:
int m_c;
int m_d;
};
BaseB::BaseB(int c, int d): m_c(c), m_d(d){
cout<<"BaseB constructor"<<endl;
}
BaseB::~BaseB(){
cout<<"BaseB destructor"<<endl;
}
void BaseB::show(){
cout<<"m_c = "<<m_c<<endl;
cout<<"m_d = "<<m_d<<endl;
}
//派生类
class Derived: public BaseA, public BaseB{
public:
Derived(int a, int b, int c, int d, int e);
~Derived();
public:
void display();
private:
int m_e;
};
Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){
cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
cout<<"Derived destructor"<<endl;
}
void Derived::display(){
BaseA::show(); //调用BaseA类的show()函数
BaseB::show(); //调用BaseB类的show()函数
cout<<"m_e = "<<m_e<<endl;
}
int main(){
Derived obj(1, 2, 3, 4, 5);
obj.display();
return 0;
}
6。虚继承
多继承时很容易产生命名冲突,
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,
这个时候类 A 中的成员变量和成员函数继承到类 D 中
变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:
void seta(int a){ B::m_a = a; }
6.1 虚继承
在继承方式前面加上 virtual 关键字就是虚继承:
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
在虚继承中,虚基类是由最终的派生类初始化的,换句话说,
最终派生类的构造函数必须要调用虚基类的构造函数。
对最终的派生类来说,虚基类是间接基类,而不是直接基类。
这跟普通继承不同,在普通继承中,派生类构造函数中只能
调用直接基类的构造函数,不能调用间接基类的。
#include <iostream>
using namespace std;
//虚基类A
class A{
public:
A(int a);
protected:
int m_a;
};
A::A(int a): m_a(a){ }
//直接派生类B
class B: virtual public A{
public:
B(int a, int b);
public:
void display();
protected:
int m_b;
};
B::B(int a, int b): A(a), m_b(b){ }
void B::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<endl;
}
//直接派生类C
class C: virtual public A{
public:
C(int a, int c);
public:
void display();
protected:
int m_c;
};
C::C(int a, int c): A(a), m_c(c){ }
void C::display(){
cout<<"m_a="<<m_a<<", m_c="<<m_c<<endl;
}
//间接派生类D
class D: public B, public C{
public:
D(int a, int b, int c, int d);
public:
void display();
private:
int m_d;
};
D::D(int a, int b, int c, int d): A(a), B(90, b), C(100, c), m_d(d){ }
void D::display(){
cout<<"m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}
int main(){
B b(10, 20);
b.display();
C c(30, 40);
c.display();
D d(50, 60, 70, 80);
d.display();
return 0;
}