C++面向对象的程序设计
1,前言
C++程序与设计是我上学期的我们专业开的一门限选课,开始就挺想学的,结果阴差阳错的没选上。“那就旁听吧”,我对自己说。两节课之后,我又咕咕了。。。这学期开了数据结构,选上后才发现C++是先选课,因此,就不得不重拾《C++程序设计(谭浩强)》开始一段一段的撸代码。写这篇博客呢,主要是为了自己做笔记,当然,若能给大家带来帮助,那我自然是双倍的快乐啦。之后呢,我也会陆续跟进老师讲的内容,写一篇关于数据结构的博客,希望以此共勉!
2,概述
以类对象为基本构成单位的程序称为基于对象的程序,而面向程序则还有更多要求。面向对象的程序设计共有四个主要特点:抽象,封装,继承和多态性。
- 抽象:类是用来定义对象的抽象类型
- 封装:函数名作为类对象的对外接口
- 继承:派生类继承基类的基本特征
- 多态性:由继承产生的不同的派生类面对同一消息会做出不同的响应
在基于对象的程序设计的基础上,利用继承和多态性,就是面对对象的程序设计。因此,就形成了新的观念,即
对象=数据+算法
程序=(对象+对象+···)+消息
3,类
3.1类的声明
格式与结构体的声明比较相似,如下
class Student //声明类
{
private:
int num;
char name[20];
char sex;
public:
void display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
};
Student stud1,stud2; //定义对象
在面向对象的程序设计中,一般把所有的数据指定为私有,把需要让外界调用的函数设为公有
typedef可以为系统固有或者自定义的数据类型取别名,但并不是定义了一种新的数据类型,对于给类,其格式为
typedef class Student STUDENT;
3.2成员函数的外部定义
如果在类的定义中既不指定public,也不指定private,则默认为私有的。其中成员函数也可以在类的外部定义,如:
class Student
{
private: //私有数据不能被外界调用
int num;
char name[20];
char sex;
public:
void display();
};
void Student::display() //“::”作用域限定符
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
Student stud1,stud2;
在类里声明函数,在类外定义是一个良好的习惯
3.3内置成员函数
函数内部定义则默认为该函数为内置函数,外部定义则需要在函数声明和定义的之前,加上关键字inline,这样,当程序要调用成员函数时,直接将代码嵌入代码的调用点,减少时间开销
class Student
{
private:
int num;
char name[20];
char sex;
public:
inline void display();
};
inline void Student::display() //“::”作用域限定符
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
Student stud1,stud2;
注意,即使时内置函数也并不占用对象的存储空间
3.4对象成员的引用
- 对象名+成员运算符,如
stud1.num=1000
- 指针,如
Student *p;cout<<p->num;
- 引用(别名),如
Student stud1;Student &k=stud1;cout<<k.num;
3.5构造函数
构造函数是一类特殊的成员函数,它不具有任何类型,不返回任何值,它不需要用户调用它,在对象建立的时候自动执行,而要实现这一点只需将成员函数与类同名。我们通过构造函数实现对象的初始化。
#include <iostream>
using namespace std;
class Box
{
public:
Box(int,int,int);
int volume();
private:
int height;
int width;
int length;
};
Box::Box(int h,int w,int len)
{
height=h;
width=w;
length=len;
}
Box::volume()
{
return (height*width*length);
}
int main()
{
Box box1(12,13,14); //类的初始化
cout<<"The volume of this box is:"<<box1.volume()<<endl;
}
我们可以根据具体情况,对构造函数进行重载,以满足对象初始化的要求
3.6析构函数
与构造函数相反,析构函数是当对象结束使命时调用,具体来说在以下几类场合调用
- 在函数中的定义的对象,当函数结束调用时,对象应该释放,在对象释放之前调用
- 静态局部对象1在函数结束调用时不释放,只有当main函数结束时调用
- 全局变量离开作用域时
- 使用delete释放动态对象时
#include <cstring>
#include <iostream>
using namespace std;
class Student
{
public:
Student(int,char[],char);
~Student(); //析构函数的声明
void display();
private:
int num;
char name[10];
char sex;
};
Student::Student(int n,char nam[10],char s)
{
num=n;
strcpy(name,nam);
sex=s;
cout<<name<<"'constructor called"<<endl<<endl;
}
Student::~Student() //析构函数的定义
{
cout<<name<<"'destructor called"<<endl;
}
void Student::display()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
}
int main()
{
typedef class Student STUDENT;
STUDENT stud1(1010,"jianjian",'m');
stud1.display();
STUDENT stud2(1011,"dongdong",'f');
stud2.display();
}
输出应为
jianjian'constructor called
num:1010
name:jianjian
sex:m
dongdong'constructor called
num:1011
name:dongdong
sex:f
dongdong'destructor called
jianjian'destructor called
Process returned 0 (0x0) execution time : 0.545 s
Press any key to continue.
3.7对象的动态建立和释放
普通变量在程序结束后才会释放,若想要随时释放。我们可以用new动态建立,如
new Box;
这时,会开辟一片没有名字的内存,并返回这片内存的地址
Box *pt;
pt=new Box(10,10,10);
3.8对象的复制
本来运算符“=”是用来单个变量赋值的,现在被重载为对类对象之间的复制
int main()
{
Box box[3]={{10,10,10},{10,10,11},{10,10,12}};
box[0]=box[2];
cout<<"The volume of this box is:"<<box[0].volume()<<endl;
}
输出为
The volume of this box is:1200
Process returned 0 (0x0) execution time : 0.050 s
Press any key to continue.
3.9静态数据成员
静态数据成员相当于优化的全局变量。它为各对象所共有,它的值对所有对象都是一样的,它的改变对所有对象也是同步的,但是不能用初始化参数表对静态数据成员进行初始化,只能对其单独初始化
#include <iostream>
using namespace std;
class Box
{
public:
Box(int,int);
int volume();
private:
static int height;
int width;
int length;
};
Box::Box(int w,int len)
{
width=w;
length=len;
}
Box::volume()
{
return (height*width*length);
}
int Box::height=10; //静态数据成员只能在类体外进行初始化
int main()
{
Box box[3]={{10,10},{10,11},{10,12}};
box[0]=box[2];
cout<<"The volume of this box is:"<<box[0].volume()<<endl;
}
3.10运算符的重载
重载的含义简单来说就是“一名多用”,格式如下
函数类型 operator 运算符名称(形参表)
{对运算符的重载处理}
如:
class Student1:public Student
{
public:
Student1(int n,string nam ,char s,int a,string ad,int n1,string nam1 ,char s1):Student(n,nam,s),monitor(n1,nam1,s1)
{
age=a;
addr=ad;
}
void display1();
void show_monitor();
Student1 operator +(Student1 &a); //对“+”重载的声明
protected:
Student monitor;
int age;
string addr;
};
Student1 Student1::operator +(Student1 &a) //重载的实现
{
Student1 b;
b.num=num+a.num;
b.age=age+a.age;
b.name="王源";
b.sex='m';
return b;
}
一般来说,C++允许重载绝大多数运算符,包括
不能被重载的运算符只有4个
4,指针
4.1对象指针
建立对象时,系统给对象分配内存时,对象空间的起始地址就是对象的指针
定义格式如下
Time t;
Time *pt;
pt=&t;
引用格式如下
(*pt).volume();
pt->volume();
4.2指向对象成员的指针
定义格式如下
void (Time::*pt)();
pt=&Time::volume;
即
数据类型名 (类名::*指针变量名)(参数列表);
指针变量名=&类名::成员函数名;
指针的定义在类中有很多要求,这些方面都要匹配
- 函数参数的类型和参数个数
- 函数返回值类型
- 所属的类
5,继承与派生
继承是利用已有的类建立新类,而派生类就是已有基类的派生
5.1派生类的声明
class Student1:public Student //声明基类为Student
{
public:
void display1(); //新增加的成员函数
private:
int age; //新增加的成员数据
string addr;
};
void Student1::display1()
{
cout<<"age:"<<age<<endl;
cout<<"address:"<<addr<<endl;
}
5.2派生类的访问属性
派生类的继承方式包括三类:公用继承,私有继承,受保护继承
在基类中的访问属性 | 继承方式 | 在派生类中的访问属性 |
---|---|---|
private | public | 不可访问 |
private | private | 不可访问 |
private | protected | 不可访问 |
public | public | public |
public | private | private |
public | protected | ptotected |
protected | public | protected |
protected | private | private |
protected | protected | protected |
5.3派生类的构造函数
声明派生类如下
class Student1:public Student
{
public:
Student1(int n,char nam[10] ,char s,int a,string ad):Student(n,nam,s) //构造函数声明格式
{
age=a;
addr=ad;
}
void display1();
private:
int age;
string addr;
};
void Student1::display1()
{
cout<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"sex:"<<sex<<endl;
cout<<"age:"<<age<<endl;
cout<<"address:"<<addr<<endl<<endl;
}
注意在声明这个构造函数时,基类的构造函数已经存在,并且基类的参数也已经存在,故直接调用实参,不再调用形参,更不再需要加上数据类型
若修改主函数为
int main()
{
Student1 a(1010,"jian",'m',21,"hit");
Student1 b(1011,"dong",'f',20,"hit");
a.display1();
b.display1();
}
则会输出
jian'constructor called
dong'constructor called
num:1010
name:jian
sex:m
age:21
address:hit
num:1011
name:dong
sex:f
age:20
address:hit
dong'destructor called
jian'destructor called
Process returned 0 (0x0) execution time : 0.254 s
Press any key to continue.
5.4子对象
在对象之中内嵌的对象,就是子对象,即对象中的对象
如在派生类的成员数据中就可以定义基类的对象
class Student
{
public:
Student(int,string,char);
~Student();
void display();
int num;
string name;
char sex;
};
class Student1:public Student
{
public:
Student1(int n,string nam ,char s,int a,string ad,int n1,string nam1 ,char s1):Student(n,nam,s),monitor(n1,nam1,s1)
{
age=a;
addr=ad;
}
void display1();
void show_monitor();
private:
Student monitor;
int age;
string addr;
};
在main函数中,运行
int main()
{
Student1 a(1010,"jian",'m',21,"hit",1009,"liu",'m');
Student1 b(1011,"dong",'f',20,"hit",1009,"liu",'m');
a.show_monitor();
a.display1();
b.display1();
}
在codeblocks中可以输出
jian'constructor called
liu'constructor called
dong'constructor called
liu'constructor called
monitor:liu
num:1010
name:jian
sex:m
age:21
address:hit
num:1011
name:dong
sex:f
age:20
address:hit
liu'destructor called
dong'destructor called
liu'destructor called
jian'destructor called
Process returned 0 (0x0) execution time : 0.061 s
Press any key to continue.
注意不能在声明派生类时初始化子对象,因为类是抽象数据类型。执行派生类的构造函数的顺序是
- 调用基类的构造函数,完成对基类数据成员的初始化
- 调用子对象的构造函数,完成子对象的初始化
- 执行派生类构造函数本身,完成派生类的数据的初始化
一般形式为:
派生类构造函数名(总参数表):基类构造函数名(基类参数表),子对象(子对象参数表)
{派生类新增的数据成员初始化语句}
这个链接有更详尽的介绍
C++有子对象的派生类的构造函数
5.5多重继承
若一个派生类需要继承几个不同的基类,则需要多重继承
格式为:
派生类构造函数名(总参数表):基类1构造函数名(参数表),基类2构造函数名(参数表),基类3构造函数名(参数表)
{派生类新增成员初始化}