目录
一:面向对象程序设计
1. 传统的面向过程的程序设计
POP(Process Oriented Programming)
2. 面向对象的程序设计
OOP(Object Oriented Programming)
3. 面向对象(Object Oriented )是认识事务的一种方法,是一种以对象为中心的思维方式
4. 面向对象的程序设计:
对象=(算法+数据结构)
程序=对象+对象+……+对象
面向对象程序设计模拟自然界认识和处理事物的方法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象(object),同类对象还可抽象出共性,形成类(class )
一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口对象之间通过消息(message)进行通讯
我们把对象之间产生相互作用所传递的信息称做消息
C++将“向对象发消息”处理成“调用对象的某个成员函数”
二:对象
世界上所有的事物都可以称为对象(Object)
对象可以是有形的如:一台电视机等。也可以是无形的如:帐户、一项记录等
一个对象就是一个独立存在的客观事物,它由一组属性和对属性进行操作的一组操作构成
属性是对象静态特征的描述
操作是对象动态特征的描述
属性和操作是对象的两大要素。如电视的属性有:品牌、尺寸、重量等。操作有:收视、选台、音量调节等
三:类
是一个抽象的概念,用来描述某一类对象所共有的、本质的属性和类行为
根据抽象的原则对客观事物进行归纳和划分
四:面向对象基本特征
抽象
封装和数据隐藏(简单介绍)
继承
多态
封装和数据隐藏:
1. 封装是指按照信息屏蔽的原则,把对象的属性和操作结合在一起,构成一个独立的对象
2. 通过限制对属性和操作的访问权限,可以将属性“隐藏”在对象内部,对外提供一定的接口,在对象之外只能通过接口对对象进行操作
3. 封装性增加了对象的独立性,从而保证了数据的可靠性
4. 外部对象不能直接操作对象的属性,只能使用对象提供的服务
抽象的设计思路:
1. 数据抽象化为属性
2. 处理过程抽象化为操作(方法)
例如:当建立一个图书管理系统时,通过直觉知道系统里一定要有图书管理员/读者/书这些对象, 而其中读者的行为含有借书/还书,学生还有其相应的学号/姓名/班级等内容
3. 用面向对象方法来认识青蛙
五:类的定义
1. C++通过建立数据类型——类来支持封装和数据隐藏
一个定义完好的类可以作为独立模块使用
2. 类的定义格式分为说明部分和实现部分
说明部分包含数据成员和成员函数说明
实现部分是用来对成员函数的定义
3. 类的一般定义格式如下:
class <类名>{
public :
<公有数据成员和成员函数>;
protected:
<保护数据成员和成员函数>;
private :
<私有数据成员和成员函数>;
};
<各个成员函数的实现>;
1. class是定义类的关键字是数据类型说明符,<类名>是一个标识符,用于唯一地标识一个类(新的数据类型),类名后面花括号扩起来的部分是类体(Class Body)
2. 类体中定义了类成员表(Class Member List)
数据成员(Data Member)
成员函数(Member Function)
3. public、protected和private是访问限定符(access specifier)
4. 一般在类体内先说明公有成员,它们是用户所关心的,后说明私有成员,它们是用户不感兴趣的,但此顺序并不是必须的
5. 在类的说明部分之后必须加分号“;”
6. 类的定义放在一个头文件中(.h),供其它需要使用该类的文件包含
7. 类的实现部分放在一个源文件中(.cpp),该文件需要包含定义类的头文件
8. 类的定义和实现可以放在一个文件里,但不提倡,因为结构不清晰,影响了程序的可读性
六:数据成员 属性
1. 类中的数据成员的类型可以是任意的,可以定义各种类型的变量、指针、数组等,甚至是其他类的对象;在说明数据成员时,一般按数据成员的类型大小,由小到大说明,这样可提高空间利用率
2. 在类的定义中只能声明数据成员,不允许对所定义的数据成员进行初始化
3. 类的数据成员只能在类里声明,类的数据成员最好都是私有的
4. 外部函数需要修改它,一般也只给它提供公有函数接口,让它通过类的公有成员函数访问类的私有数据
5. 数据成员也可以放在public部分,但不提倡
七:成员函数 方法
1. 成员函数可以直接使用类定义中的任一成员,可以处理数据成员,也可调用成员函数
2. 类的成员函数定义通常可采取两种方式,即外部定义和内部定义(不推荐!影响到了程序的可读性)
3. 成员函数的实现一般放在类的外面,在类里面对函数的原型进行声明
4. 成员函数的实现在类的定义外面时,必须声明其所属,在函数名前加类名和作用域运算符“::”,用来表示某个成员函数属于哪个类的,定义格式如下:
返回类型 类名::成员函数名(参数说明)
{函数体 }
5. 类成员函数的实现也可以放在类里,不需要加类名和作用域运算符
八:访问控制
三个访问说明符:public、private和protected
作用:控制对类的成员的访问
在用class定义的类中,默认的访问方式是private(结构体的默认访问方式是public)
在类的定义中,三个访问说明符都可以使用多次(不提倡)
它们的作用域是从该说明符出现开始到下一个说明符之前或类体结束之前结束
private:类的私有成员只能被类的成员函数、友元函数、友元类访问,类外无法访问他们
protected:类的受保护成员能被类的成员函数、友元函数、友元类和派生类成员函数访问
public:类的公有成员可以被类的成员函数、友元函数、友元类所有能访问到类的对象的外部程序代码直接访问,这部分往往是一些操作(即成员函数)
类的公有成员是类的对外接口
类定义了函数(方法)和数据(属性)的访问控制属性
哪些成员可被外界直接访问
哪些成员只能被自己的成员函数访问
封装和信息隐藏技术使类变得很安全
私有数据成员只有类自己的成员函数能访问
只要确保成员函数操作的合理合法性,对象就是安全的
提高了程序的可维护性
九:对象的定义
对象是类的实例;对象是属于某个已知的类
定义对象之前,一定要先定义好该对象的类
定义好的类可以作为新的数据类型来使用
声明的语法:类的名称 对象的名称;
示例如下:
Stack oneStack; //Stack类型的对象
Stack arrayOfStack[10]; //Stack类型的对象数组
Stack *pStack=&oneStack; //Stack类型的指针
Stack &s = oneStack; // 引用一个Stack对象
十:构造函数
对于对象的初始化,采用构造函数(constructor)
编写一个或一组构造函数
构造函数是特殊的成员函数
构造函数的功能:
为对象分配空间
对数据成员赋初值
请求其他资源
如何声明构造函数:
函数名和类名完全相同
不能定义构造函数的类型(返回类型),也不能使用void
构造函数应声明为公有函数,但它不能像其他成员函数那样被显式地调用
构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)
创建类的对象时,自动调用类的构造函数
创建一个对象时只调用一个构造函数(根据参数列表)且只在创建时调用一次
十一:重载构造函数
在一个类需要接受不同的初始化值时,就需要编写多个构造函数, 他们之间构成重载关系
重载构造示例:
class Student{
private:
char m_No[4],m_Name[21];
int m_Age,m_Score[5];
public:
Student(); //默认构造函数
Student(char *name,int age,int score[5]);
};
十二:构造函数的种类
1. 普通构造函数:有两个或两个以上参数的构造函数
2. 默认构造函数
3. 复制(拷贝)构造函数
4. 类型转换构造函数
十三:默认构造函数
哪种构造函数是默认构造函数?
没有参数或者所有的参数都有默认值的构造函数
类里一定会有默认构造函数吗?
如果类中没有声明构造函数,编译器将自动产生一个公共的默认无参数构造函数;
如果存在一个构造函数声明,那么编译器不会产生一个默认的构造函数
默认构造函数何时调用?
没有指明初始化对象时,便按默认构造函数来初始化该对象
例如:Person p1,p2; //使用默认构造函数对p1和p2进行初始化
一个类中只能有一个默认构造函数
(如果一个构造函数的所有参数均有默认值,这时再定义无参数的默认构造函数无意义,会产生调用时二义性)
十四:构造函数中使用默认参数
可以为类的成员函数指定参数的默认值,包括构造函数在内
1. 要把默认参数值放在函数声明中,而不应放在函数定义中,如下
redefinition of default parameter
2. 如果一个构造函数的所有参数均有默认值,这时再定义无参数的默认构造函数无意义,将产生调用时二义性,如下
ambiguous call to overloaded function
构造中使用默认参数,示例如下
class CCube{
public:
CCube(double len);
CCube(int len=12);
//CCube();
private:
double m_len;
};
CCube::CCube(int len){m_len=len;}
十五:拷贝(复制)构造函数
特殊的构造函数
功能:
使用一个已经存在的对象来初始化一个新的本类的对象
声明:
只有一个参数并且参数为该类对象的引用
class 类名{
public:
类名(类名 &对象名);};
如果类中没有说明复制构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员
重点:浅复制和深复制
浅复制:将对象数据成员的值进行简单的复制(最好利用系统自动生成的复制构造函数,已完成浅复制
深复制:不仅将对象数据成员的值进行复制,而且对指针型数据成员生成新空间,然后复制对应的值
拷贝构造函数使用示例一:
#include <string.h>
#include <iostream.h>
class Employee{
public:
Employee(char *n);
Employee(Employee &emp);
~Employee();
private:
char *name;
};
Employee::~Employee();
{//析构函数
delete [] name;
}
Employee::Employee(char *n){
name = new char[strlen(n)+1];
strcpy(name,n);
}
Employee::Employee(Employee &emp){
int len=strlen(emp.name);
name=new char[len+1];
strcpy(name,emp.name);
}
int main(){
Employee emp1(“zhang”);
Employee emp2(emp1);
//Employee emp2=emp1;
return 0;
}
拷贝构造函数使用示例二:
class Student{
private:
char m_No[4],m_Name[21];
int m_Age,m_Score[5];
public:
Student(); //默认构造函数
~Student(); //默认析购函数
Student(const Student &stu);
Student(char *name,int age,int score[5]);
PrintStudent();
};
Student:: Student(Student &stu){
m_Age=stu.m_Age;
strcpy(m_Name,stu.m_Name);
strcpy(m_No,stu.m_No);
memcpy(m_score,stu.m_score,sizeof(stu.m_score));
}
Student::Student(char *name,int age,int score[]){
m_Age=age;
strcpy(m_Name,name);
for(int i=0;i<5;i++){
m_Score[i]=score[i];
}
}
Student::PrintStudent(){
cout<<"name:"<<m_Name<<'\t';
cout<<"age:"<<m_Age<<'\t';
cout<<"score:";
for(int i=0;i<5;i++){
cout<<m_Score[i]<<'\t';
}
cout<<endl;
}
void main(){
int score[5]={100,100,100,100,100};
Student stu1("Tom",12,score);
Student stu2(stu1);
cout<<"stu1="<<endl;
stu1.PrintStudent();
cout<<"stu2="<<endl;
stu2.PrintStudent();
}
/*注意:对象中包含动态分配的数据成员时,复制构造函数的实现不能只是简单的赋值。应该先为当前对象的数据成员分配新的空间,再将参数对象中相应的数据复制过来*/
十六:拷贝构造函数的其他作用
当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用
这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中
理所当然也调用拷贝构造函数
当函数的返回值是类对象,函数执行完成返回调用者时使用
理由也是要建立一个临时对象中,再返回调用者
为什么不直接用要返回的局部对象呢?
因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中
所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象
如果返回的是变量,处理过程类似,只是不调用构造函数
十七:转换构造函数
提供了单个参数的构造函数
相当于将一个其他类型的数值或变量转换为自身类型的数据
类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型
转换构造函数,示例如下
class CCube{
public:
CCube(double len){
m_len=len;
}
private:
double m_len;
};
int main(){
CCube cube=12.4;
cube=11;//隐式转换
return 0;
}
十八:explicit关键字
只提供给类的构造函数使用的关键字
编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象
error C2679: binary '=' : no operator defined which takes a right-hand operand of type 'const int' (or there is no acceptable conversion)
示例如下:
class CCube{
public:
explicit CCube(double len){
m_len=len;
}
private:
double m_len;
};
int main(){
CCube cube=12.4;//不允许
//cube=11;//不允许
return 0;
}
十九:析构函数
特殊的成员函数
作用:
撤销对象前做清理工作,一般是释放对象在生存期间动态申请的空间
当对象超出其定义范围时(即释放该对象时),编译器自动调用析构函数
对象被定义在函数体内,则当这个函数结束时,该对象的析构函数被自动调用
对象是使用new运算符动态创建的,在使用delete运算符释放它时,delete将会自动调用析构函数
函数名和类名相似(前面多了一个字符“~”)
没有返回类型
没有参数
析构函数不能被重载
如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:
类名::~默认析构函数名( )
{
}
默认析构函数是一个空函数
构造和析构函数使用示例如下:
#include <iostream.h>
class Student{
public:
Student();
~Student();
private:
char *m_name;
};
Student :: Student(){
cout<<“Default constructor called”<<endl;
m_name=new char[5];
strcpy(m_name,”abc”);
}
Student ::~Student(){
cout<<“Free”<<endl;
delete []char;
}
void Test()
{
Student stu;
}
void main()
{
Test();
Student *stu_ptr=new Student;
}