1、什么是类(class),什么是对象(obj)?
C语言:int a = 10
int表示整数类,这是系统自带的一个类,a则是这个类的一个对象
float,char都是系统自带的类
面向对象编程
创建自己的类
创建自己的对象
例:学生类student,对象张三李四,好比int a;int b
2.如何创建自己的类
形式:
class student
{
string name;
int age;
}
类名:student
类里两个内容,年龄名字,叫做类的成员数据,或者叫属性,类比int a = 10,10就是a的属性
到这里为止,类和C语言的结构体还是一样的
3.共有和私有的初步概念
在类的定义里,形式如:
class student
{
public:
string name;
private:
int age;
}
name共有
类的外部(主函数里或者其他类)可以读写name的值
age私有
类的外部外面看不到
可以只有public
可以将名字和年龄都写在public,初学者可以先不写private
没有private的类相当于结构体
4.如何使用一个类
类被定义后,就创建类的对象,形式如int a一样
student aa;
创建一个student类的对象,名字叫aa
aa.age = 20;
给aa的属性赋值,和结构体一样
5.什么是成员函数
成员函数的重载
和C语言普通函数一样,成员函数也可以重载
不同函数可以有相同的函数名,通过不同的输入参数来识别,如:
class student
{
public:
int age;
string name;
bool set(int a); //两个函数名字一样,但输入格式不一样
bool set(string a); //这个函数定义略过,name = a;return true;
即可
}
第一部分阶段总结
到此为止已经讲完全部的基本概念:
1.类(学生, 类定义里要写成员和方法的声明)
2.对象(aa)
3.成员数据(年龄和名字,又叫属性)
4.成员函数(set,给年龄赋值,又叫方法,具体内容写在类的外面)
一个面向对象的代码最少有三部分:
1.类的定义
2.成员函数的定义
3.创建对象然后调用方法
6.构造函数
student aa,创建aa对象,aa有默认值吗?
如果有构造函数,那aa就有默认值
为什么要构造函数?
建立一个对象aa,要给他的年龄和对象分别赋值,就需要set(“张三”);set(20);
使用构造函数后,即使你有再多的成员数据,不用一一写了
构造函数的本质即对象初始化
构造函数的名字必须与类名一致
class student //类定义里先声明构造函数
{
public:
int age;
string name;
student(); //构造函数,没有输入值和返回值,并且名字和类的名字一样
}
student :: student () //构造函数定义,特点就是冒号两边是一样的,注意这个函数没有输入值
{
age = 20; //构造函数里,可以对全部的成员数据给一个默认值
name = "张三";
}
//下面是主函数
student aa; //新建一个student类的对象,名字叫aa,这里不用再人工赋值年龄和名字了,
//将自动调用构造函数给年龄和名字默认值
7.带参数的构造函数
前面的构造函数没有输入值,年龄只能是20,改进一下
class student //类定义里先声明构造函数
{
public:
int age;
string name;
student(); //构造函数,没有输入值和返回值,并且名字和类的名字一样
student(int a, int b); //同名的构造函数声明,即重载,带2个输入参数,即名字和年龄
}
student :: student () //构造函数定义,特点就是冒号两边是一样的,注意这个函数没有输入值
{
age = 20; //构造函数里,可以对全部的成员数据给一个默认值
name = "张三";
}
student :: student (int a, string b) //构造函数定义,特点就是冒号两边是一样的,
//注意这个函数没有输入值
{
age = a; //构造函数里,可以对全部的成员数据给一个默认值
name = b;
}
//下面是主函数
student aa; //新建一个student类的对象aa,自动给aa属性默认值
student bb(25, "李四"); //析构函数重载,新建一个student类的对象bb,同时动态输入名字和年龄
8.析构函数
析构函数销毁内存数据
对象aa被创建后,其数据就一直在内存中了,这块内存什么时候被释放?就是使用析构函数的时候。
但主函数中并不能直接运行student aa;delete aa;
只有student *p = new student(20, "张三");
delete p才将自动调用析构函数;
写法和构造函数类似
student :: ~student() //析构函数名字比构造函数多个~号,没有输入、返回值
{
cout << "delete object"; //C++中cout相当于C语言中的printf就是打印
}
9.常成员函数,关键字const
只读取属性,而不修改属性,就是常成员函数
class student
{
public:
int age;
string name;
bool set(int i); //成员函数的声明,又叫方法
bool read() const; //后面加了const,表示这个函数只读不写
}
bool student :: read() const //常成员函数内容,后面加了const,只读不写
{
cout << name; //打印出姓名
cout << age; //打印出年龄
return true;
}
student aa; //创建对象
aa.read(); //在创建aa后,调用read方法
实际可以不使用const,普通方法也可以读数据,加const只是安全起见,防止意外修改数据
更加不正规的写法就是直接读取:aa.age,在正规程序中还是会使用const来读读取
10.静态成员函数,静态成员数据,关键字static
静态成员数据
一个类可以创建多个对象,int a; int b; int c; int d;student类可以不停新建aa bb cc dd对象
需要一个变量cnt = 4 来表示程序已经创建了4个对象了,cnt这个数,与student类有关,但又不属于aa bb cc dd 中间任何一个对象
这种描述全局,又与某个对象属性无关的,叫做静态成员数据
读取静态成员数据的方法,叫静态成员函数
在类定义中加入静态成员数据,静态成员函数
class student //类定义
{
public:
int age; //成员数据,年龄
string name; //成员数据,名字
student(); //无参构造函数
static int cnt; //静态成员数据,统计又多少个对象
static int count(); //静态成员函数,返回有多少个对象
}
如何定义静态成员:
student :: student () //构造函数定义
{
age = 20; //构造函数里,可以对全部的数据给一个默认值
name = "张三";
cnt = cnt + 1; //统计
}
int student :: count () //静态成员函数定义,注意这里不写static了
{
return cnt; //返回静态成员函数,统计有多少个对象
}
如何使用静态成员函数,下面是主函数:
student aa; //创建aa对象
student bb; //创建bb对象
aa.count(); //调用count方法,返回静态成员数据cnt=2,已建立两个对象,这行与下一行一样
bb.count(); //注意不论是aa还是bb,返回值是一样的,静态成员不依赖于某个对象
student :: count (); //返回静态成员数据cnt=2,这个写法与aa.count()效果完全一样
//只有不依赖于对象的静态函数才可以这么写
第二部分阶段总结
第二阶段讲了类的成员函数,又叫方法:
1.普通成员函数
bool student :: set(int a)
2.构造函数,自动初始化对象
student :: student()
3.带参数的构造函数,动态初始化对象
student :: student (int a, string b)
4.析构函数,销毁对象
student :: ~student()
5.常成员函数,只读不写
bool student :: read() const
6.静态成员函数,依赖于类,但不依赖于某个对象
static int count()
11.再谈谈public和private
设private的目的
高手:对类的外部隐藏了类内部的工作机制,即所谓“封装性”
新手:public的内容,可以在主函数看到,private内容只能在类的内部看到
简化的例子
class student //类定义,没写全,为了突出重点省略了构造函数和属性
{
public:
void print_name(); //共有的方法
private:
void print_age(); //私有的方法
};
void student::print_name() //定义共有方法
{
cout << "张三";
}
void student::print_age() //定义私有方法
{
cout << "20";
}
//以下是主函数
student aa; //创建对象aa,构造函数省略
aa.print_name();//调用公有方法,打印出张三
aa.print_age();//调用私有方法,不允许,这行会报错,提示student类不存在print_age这个方法
既然私有方法不能被调用,那还有什么意义?
可以用,有条件
核心思想:通过共有方法的外壳调用私有方法
修改的例子
class student //类定义,没写全,为了突出重点省略了构造函数
{
public:
void print_age_public(); //公有的方法,外面看得到
private:
void print_age_private(); //私有的方法,外面看不到
};
void student::print_age_public() //定义公有方法
{
print_age_private(); //重点,通过公有方法外壳调用私有方法
}
void student::print_age_private() //定义私有方法
{
cout << "20";
}
//以下是主函数
student aa; //创建对象aa,构造函数省略
aa.print_age_public();//通过公有函数外壳,调用私有,打印出年龄20
aa.print_age_private();//直接调用私有方法,不允许,这行会报错
12.正规程序的类定义写法
class sudent
{
public:
bool set(int a); //两个函数名字一样,但输入格式不一样
bool set(string a);
bool read() const; //后面加了const,表示这个函数只读不写
sudent();
sudent(int a, int b);
private:
int age;
string name;
};
一般把“方法”全部放在public中
“属性”放在private中
目的是防止类的外部随意篡改数据
通过函数来修改数据时,一般都具有防错机制,满足了条件才可以让你改
13.友元类,关键字friend
14.类的派生继承初步
什么是派生
把student类细分成本科生和研究生
本科生和研究生也是学生,是学生的子类
学生是父类也叫基类或超类
所谓派生,是相对于父类而言的
所谓继承,是相对于子类而言的
派生出本科生的写法:
class undergraduate : public student //本科生类定义,冒号后面表示从student类共有派生而来
{
public:
string course; //新增定义了一个公开的字符串属性course,比如本科生的大学物理课程
};
派生出研究生的写法:
class postgraduate : public student //研究生类定义,冒号后面表示从student类共有派生而来
{
public:
string research; //新增定义了一个和本科生不一样的字符串属性research,
//比如研究生的方向:芯片设计
};
对比研究生本科生两个类:
都没写两个基本属性name和age,因为根本不用写,子类自动继承父类的属性和方法
除了共有的name和age,也有不同的东西,这是非常重要的类的派生特性,除了继承来的,还能自己发展壮大,可以有自己的属性,也可以有自己的方法
子类没有构造函数,他的对象怎么被创建?name和age是否有值呢?
答案是肯定的
postgraduate bb; //创建研究生对象,此时子类无构造函数,
//系统将调用父类student的无参构造函数给name和age赋一个默认值。
//其默认值在之前小节中,也就是“张三”,age = 20
bb.set(25); //postgraduate类本身没有set方法,
//这里调用了之前小节中父类student的方法,对age赋值
//通过上面两行,说明了子类可以自动继承父类的属性和方法
15.类在不同情况下的继承,关键字public,private,protect
public,private,protect
除了public和private,还有一个和他们平级的关键字,protect(保护)
之前没有说protect,是因为在没有派生继承的情况下,protect和private效果完全一样
研究生类的定义:
class postgraduate : public student {......}
为什么前面要加public这个字呢?
public表示公有继承,只继承student的共有部分(age和name),并且age和name也将作为研究生类的public部分
研究生类是无法继承也无法访问student类的私有部分的
将父类student类定义的private替换为protect
对student本身无任何变化
当class postgraduate:public student{......}时候,除了public被继承了,protect(私有)也研究生被继承了
student的protect内容被继承到了postgraduate的protect中
共有继承总结:
父类student,public内容A,private内容B,子类postgraduate公有继承,则只有public内容A
父类student,public内容A,protect内容B,子类postgraduate公有继承,则子有public内容A+protect内容B
私有继承和保护继承
class postgraduate:public student{......} public替换成private和protect
父类public内容A,private内容B,子类私有继承,则子类只有内容A,且为private
父类public内容A,protect内容B,子类保护继承,则子类有内容A、B,其都为protect
在保密优先级上,private最私密,protect其次,public最公开。private里的内容不论如何都不能被继承
16.子类的构造函数
子类可以有自己的构造函数
子类没有构造函数,系统会调用父类student的构造函数
但是,不论子类有没有自己的构造函数,在创建子类对象时,父类的构造函数都会被调用
研究生类不带参数构造:
class postgraduate : public student //研究生类定义,表示从student派生而来
{
public:
string research;//研究生的公有成员
postgraduate(); //无参构造函数声明
postgraduate(int a, string b, string c); //带参数的构造函数声明,
//参数为年龄姓名研究方向
};
postgraduate::postgraduate() //无参函数构造定义
{
research = "asic design";
}
//主函数
postgraduate bb; //创建研究生对象bb,调用无参构造函数
创建子类bb对象时,系统首先自动运行父类student构造函数,对age和name赋值,随后运行子类postgraduate构造函数,对bb对象进行拓展,增加research内容
话句话说,子类是不能继承父类的构造函数的,只能调用父类构造函数
带参数的子类构造函数
定义和声明长得不一样,后面多了东西
postgraduate::postgraduate(int a, string b, string c) //带参函数构造定义
{
research = c;
}
//主函数
postgraduate bb(25, "李四", "ASIC design"); //创建研究生对象bb,调用有参构造函数
程序会先调用父类student构造函数,把25和李四两个值传入父类student带参数的构造函数
随后把参数c=asic design传给bb自己的research属性,这样实现可对研究生对象的全动态的初始化赋值
17.多态的初步
方法真的是专属于某个类的吗?
第一次介绍类的方法(成员函数)时说,方法是专属于某个类的,举了例子,int a=1; int b=2;则a+b=3;
加法专属于整数类的,如果是string a;string 吧,显然是不能用加法的(char a + char b可以)
颠覆一下这个概念
在比较新的编程语言中,比如python,两个字符串是可以执行加法的,形式如下:
a = "xxxx"
b = "yyyy"
print (a + b)
这里并不会报错,会打印出字符串“xxxxyyyy”,就是拼接了两个字符串,是不是很奇怪?
这就是面向对象编程大名鼎鼎的”多态“
先给一个不太严谨的说法,其思想是,不同类的对象也可以用相同的方法,这里的相同是指相同的名字方法,而方法里面的内容具体干了什么,是不一样的
比如当a,b是数字,符号‘+’执行传统意义的加法,当a,b是字符串,符号‘+’执行字符串拼接,系统将根据当前情况智能选择采用哪种方法
上面这个不严谨的说法,在python等新兴语言中是真确的
在C++中略有不同,C++比较古板,如果要实现相同的函数名实现不同的内容,有3种方法:
重载
隐藏
覆盖(override,又叫重写,这个才是C++的多态)
重载
python a+b这个例子,C++中看上去更接近重载
重载两个函数的参数必须是不一样的
隐藏和覆盖的同名函数的参数可以完全一样
不依赖于面向对象,依赖于编译器
隐藏
在父类student和子类postgraduate中都定义一个函数study(),输入参数格式相同不同都可以
class student//类定义,没写全,为了突出重点省略了构造函数和属性
{
public:
void study(bool a) { cout << "好好学习"; }
};
class postgraduate : public student//类定义,没写全,为了突出重点省略了构造函数和属性
{
public:
void study(int b) { cout << "芯片设计"; }
};
//下面是主函数
postgraduate bb;//子类对象
student aa;//父类对象
bb.study(2);//研究生对象调用研究生的study方法,参数为int,打印出芯片设计
aa.study(true);//学生对象调用学生的study方法,参数为bool,打印出好好学习
bb.study(true); //这行出错,研究生对象,但参数为bool,本来应该重载父类的study方法。
//父类方法被隐藏了招不到对应的方法,这就是隐藏和重载的区别
18.类的指针
讲真正的多态前必须先讲类指针
实际程序中,大量使用类指针
不考虑继承,类指针与结构体指针类似
student *p; //新建一个student类指针
student aa;//新建aa对象
p = &aa; //p指针指向aa对象
p->name; //和aa.name一样,返回aa和name值
p->study(); //相当于aa.study(),对aa执行成员函数
实际程序中使用更高级写法:
student* p = new student(20, "张三");
delete p; //调用析构函数
把继承考虑进去,复杂一点:
类定义省略,且只考虑=共有继承
student* p1; //新建一个student父类指针
postgraduate* p2; //新建一个postgraduate子类指针
student aa; //新建父类对象aa
postgraduate bb; //新建子类对象bb
p1 = &aa; //父类指针指向父类对象,可以
p2 = &bb; //子类指针指向子类对象,也可以,下面颠倒一下试一试
p1 = &bb; //父类学生指针指向子类研究生对象,可以
p2 = &aa; //子类研究生对象指向父类学生对象,这里报错
父类指针是可以指向子类成员
因为研究生一定是学生的一种
反过来则不行,学生可不一定都是研究生
但p1->research会报错
虽然父指针指向了postgraduate,但不能调用postgraduate中的属性和方法,父类指针指向子类时,仍然只能用父类中的方法
19.真正的多态与虚函数(覆盖),关键字virtual
分别在student,undergraduate,postgraduate建一个方法
都叫study,但每个study学习的内容是不一样的
在所有子类父类study声明前面加上关键字virtual,表示这是一个虚函数
class student
{
public:
virtual void study();//声明study方法,注意前面加了virtual,是个虚函数,并且没有参数
};
void student::study()//这里没有virtual,长得和普通函数一样
{
cout << "好好学习";
}
//
class postgraduate : public student//从student派生而来
{
public://研究生的共有成员
virtual void study(); //声明study方法,注意前面加了virtual,是个虚函数,也没有参数
};
void postgraduate::study() //注意这里没有virtual,是个虚函数,也没有参数
{
cout << "芯片设计"; //研究生学习芯片设计
}
class undergraduate : public student//从student派生而来
{
public://本科生的共有成员
virtual void study(); //声明study方法,注意前面加了virtual,是个虚函数,也没有参数
};
void undergraduate::study() //注意这里没有virtual,是个虚函数,也没有参数
{
cout << "大学物理"; //本科生学习芯片设计
}
//在三个类中都设置了同名虚函数,并且他们的输入参数都是一样的也就是没有参数,
//这点和重载不同,下面时主函数
student aa;//学生对象aa
postgraduate bb;//研究生对象bb
undergraduate cc;//本科生对象cc
student* p;//定义父类指针,学生类指针
p = &aa;//指针指向学生对象
p->study();//调用学生的study方法,打印出好好学习
p = &bb;//指针指向子类对象
p->study();//父类指针调用子类研究生的方向,打印出芯片设计,就是多态
p = &cc;//指针指向另一个子类对象
p->study();//父类指针调用子类本科生的方法,打印出大学物理,就是多态
C++多态的核心,就是只要在程序开始时候设置一个父类指针,之后这个指针可以动态的指向不同的类,并且指针还是可以动态的调用不同的类的方法,从而实现可不同数据类使用相同的方法,重载时编译时决定,多态时运行时决定
20.纯虚函数与抽象类
抽象类
学生是一个类,研究生本科生或者小学生都是学生类的子类
现实中,如果一个学生对象张三,那张三一定是研究生本科生或者小学生中的一员
单纯只属于学生类而又不属于任何一个子类的对象张三在现实中是不存在的
学生类不应该具有自己的对象,于是学生类被叫做抽象类
class student
{
public:
student(); //抽象类也是有构造函数的
student(int a, string b);//构造函数重载
virtual void study() = 0;//声明study方法,注意前面加了个virtual还有=0,
//这表明这是纯虚函数
};
当设置了virtual void study () = 0;后
student就是一个抽象类
study()只有声明,没有定义,其具体内容靠子类的多态来实现
依然可以创建一个student的指针,但不能创建student对象了
student aa; //这里报错,不允许创建抽象类对象
student *p;//可以创建抽象类对象
postgraduate bb;//创建子类对象
p = &aa;//父类指针指向子类
p->study();//通过多态调用子类的study方法
使用了抽象类后,父类student依然可以有年龄姓名等基本成员,这些成员是所有学生都有的,但父类没有了自己的study方法”好好学习“了,因为研究生本科生中小学生每个阶段都有自己的学习内容,所以具体是study函数内容,靠子类去集体实现
21.子类的析构与虚析构函数
第三部分阶段总结
公有和私有:
共有部分可以随便使用,私有函数和属性需要在类的内部通过共有外壳来调用
类的派生与继承:
子类可以获得父类的成员和方法,并且还能增加自己的成员和方法
类的继承public,private,protect分不同情况
子类构造函数:
创建对象时,会先调用父类构造函数,随后再调用子类构造函数
实现相同函数名执行不同的内容
1.重载;2.隐藏;3.覆盖(多态)
什么是多态:
在父类子类所有函数声明都加virtual,并且定义父类指针
随着指针指向不同的对象,程序会自动找不到不同类里的同名方法
什么是抽象类
父类只是拥有一些共有的属性和方法,比如年龄姓名,但并不能创建自己的对象,创建对象依赖于子类实现
写在最后:多态,指针和新兴编程语言
多态指针和新兴编程语言
继承、封装、多态是面向对象的三大法宝,也是所有编程语言通用的
C++的多态依赖于指针实现,而新兴语言java,python最大的革新就是取消了指针,可以说是解放了程序员,那他们是怎么实现多态的呢?
其实虽然执政被隐藏了,但内存在,指针就在,高级语言中,本质的说,变量名就是指针,但你写下a=10,则a本省就是一个指针,指向内存中的地址,这个地址存放了数值10,当再对a进行赋值时,比如a=15,这时指针讲指向内存的一个新地址,而原来的10依然存放于内存中的老地址里没有被复写掉
因此,新兴语言的赋值本质上是在赋指针。既然变量名就是指针了,实现多态就非常方便,不用再像C++这样定义父指针指向子类对象了,系统根据传入的变量名也就是指针来判断调用哪个函数,这就是为什么再多态的初步中举的一个例子:python字符串a+b就是多态,而C++就是重载的原因
即C中,int a =10; int是数据类型, a = ”aaa“, 对a重新赋值不同类型,不允许这样写
python中,无需定义数据类型,a = 10, a的本质是指针,a = ”aaa“,就是对a重新赋值不同类型,可以,指针指向新的区域
python中a+b,本质上传入参数不是数据类型而是指针