最后更新于 2021-11-18 at 22:10:23 CST
1 绪论
-
什么是面向对象的方法? 首先,它将数据及对数据的操作方法放在一起,作为一个互相依存、不可分离的整体——对象。 对同类型对象抽象出其共性,形成类。 类中的大多数数据,只能通过本类的方法进行处理。 类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信 -
面向对象的基本概念
-
对象 面向对象方法中的对象,是系统中用来描述客观事物的一个实体,它是用来构成系统的一个基本单位。 对象由一组属性和一组行为构成 -
类 面向对象中的“类”,是具有相同属性和服务的一组对象的集合。 -
封装 封装是面向对象的一个重要原则,就是把对象的属性和服务结合成一个独立的系统单位,并尽可能隐藏对象的内部细节。 -
继承 特殊的类的对象拥有其一般类的全部属性和服务,称作特殊类的对一般类的继承。 -
多态性 多态性是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。 -
C++两个主要特点——[ 尽量兼容C,支持面向对象的方法 ] -
面向对象程序设计的基本特点
-
抽象 面向对象方法中的抽象,是指对具体问题(对象)进行概括,抽出一类的对象的公共性质并加以描述的过程 -
封装 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的函数代码进行有机的结合,形成“类”,其中的数据和函数都是类的成员。 -
继承 C++中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。 -
多态 多态是指一段程序能够处理多种类型对象的能力。在C++语言中,这种多态性可以通过强制多态、重载多态、类型参数化多态、包含多态4种形式来实现。 -
对象和类的联系 类是对象的抽象;对象是类的一个特定实例。
2 参数传递
-
值传递不影响实参,引用传递和指针传递可以影响实参 -
值(val)传递 #include <iostream>
using namespace std;
void swap(int i,int j){
int temp;
temp = i;
i = j;
j = temp;
cout<<"swap in the function: "<<
"i="<<i<<" j="<<j<<endl;
}
int main(){
int a,b;
a = 1;
b = 2;
swap(a,b);
cout<<"out the function: "<<
"a="<<a<<" b="<<b<<endl;
}
-
引用(reference)传递 #include <iostream>
using namespace std;
void swap(int &i,int &j){
int temp;
temp = i;
i = j;
j = temp;
cout<<"swap in the function: "<<
"i="<<i<<" j="<<j<<endl;
}
int main(){
int a,b;
a = 1;
b = 2;
swap(a,b);
cout<<"out the function: "<<
"a="<<a<<" b="<<b<<endl;
}
-
指针(pointer)传递 #include <iostream>
using namespace std;
void swap(int *i,int *j){
int temp;
temp = *i;
*i = *j;
*j = temp;
cout<<"swap in the function: "<<
"i="<<*i<<" j="<<*j<<endl;
}
int main(){
int a,b;
a = 1;
b = 2;
swap(&a,&b);
cout<<"out the function: "<<
"a="<<a<<" b="<<b<<endl;
}
3 内联函数
-
为什么使用内联函数 内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。 如此,节省了参数传递、控制转移等开销。 -
使用内联函数的条件 通常内联函数应该是比较简单的函数,结构简单,语句少。如果将一个复杂的函数定义为内联函数,反而会造成代码膨胀,增大开销。 -
将一个函数声明为内联函数——使用关键字inline inline [returnType] [functionName](args){
[function body];
}
-
实例——根据圆的半径求其面积 #include <iostream>
using namespace std;
const double PI = 3.1415926;
inline double getArea(double radius){
return PI*radius*radius;
}
int main(){
double radius = 2.5;
double area = getArea(radius);
cout<<area<<endl;
return 0;
}
4 带默认形参值的函数
-
**函数在定义时可以预先声明默认的形参值。**调用时如果给出实参,则用实参来初始化形参,如果没有给出实参,则采用预先生命的默认形参值。例如 #include <iostream>
using namespace std;
inline double getGravity
(double weight,double gravition_acceleration=9.80665);
int main(){
double m0 = 10.0;
cout<<"m0="<<m0<<endl;
cout<<"gravition_acceleration=9.80665 -> "<<getGravity(10.0)<<endl;
cout<<"gravition_acceleration=10.0000 -> "<<getGravity(10.0,10.0)<<endl;
}
inline double getGravity
(double weight,double gravition_acceleration)
{
return weight*gravition_acceleration;
}
-
两个注意事项
-
在相同的作用域内,不允许在同一个函数的多个声明中对同一个参数的默认值重复定义,即使前后定义的值相同也不行。 -
反过来,若作用域不同,前后定义的默认值不同是允许的。 例子,在类内和类外定义带默认参数的函数 #include <iostream>
using namespace std;
inline int fun(int i=0){
return i;
}
class Samp1e {
public:
inline int fun(int j=1){
return j;
}
private:
int var;
};
Samp1e s;
int main(){
cout<<"member function return: "<<s.fun()<<endl;
cout<<"function out of the class return: "<<fun()<<endl;
}
5 函数的重载
-
概念 两个以上的函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载。 -
例子: #include <iostream>
using namespace std;
int additionOfSquare(int i,int j){
cout<<"int"<<endl;
return i*i+j*j;
}
double additionOfSquare(double i,double j){
cout<<"double"<<endl;
return i*i+j*j;
}
int main(){
double r = 2.71828, s = 3.1415926;
cout<<additionOfSquare(r,s)<<endl;
int u = (int) r,v = (int) s;
cout<<additionOfSquare(u,v)<<endl;
}
-
注意 当使用带有默认形参值的函数重载形式时,需要注意防止二义性。例如下面的两个函数原型,在编译时无法区别为不同的重载形式。 void fun(int length,int width=2,int height = 3);
void fun(int length);
再如 int additionOfSquare(int i,int j){
cout<<"int"<<endl;
return i*i+j*j;
}
double additionOfSquare(double i,double j){
cout<<"double"<<endl;
return i*i+j*j;
}
调用 int main(){
double r = 2.71828;
int u = (int) r;
additionOfSquare(r,u);
}
也会产生歧义。
6 类和对象
-
序言
-
类的定义 class [className]{
public:
[external interface];
protected:
[protected members];
private:
[private members];
};
-
类成员的访问控制
| 派生对象 | 外部 | 友元 |
---|
public | √ | √ | √ | private | X | X | √ | protected | √ | X | √ |
-
对象 略; -
成员函数
-
成员函数的定义 returnType className::functionName(args...){
function body;
}
在类内定义成员函数时,可以不指明该函数属于哪个类 returnType functionName(args.){
function body;
}
-
成员函数的调用 class A{
public:
void fun();
}
void A::fun(){
cout<<"A"<<endl;
};
调用的两种方式:
A a;
a.fun();
A *pa = new A;
pa->fun();
delete pa;
-
成员函数调用中的目的对象 调用一个成员函数与调用普通函数的差异在于需要使用点运算符(".")指出调用所针对的对象,这一对象在本次调用中称为目的对象。 例如使用 a.fun() 调用fun()成员函数时,a 就是这一调用过程中的目的对象。 -
带默认形参值的成员函数 和普通函数相差无几,略; -
内联成员函数 略;
7 构造函数和析构函数
-
总概 在定义对象的时候进行的数据成员设置,称为对象的初始化。在特定对象使用结束时,还经常需要进行一些清理工作。C++程序中的初始化和清理工作,分别由两个特殊的成员函数来完成,他们就是构造函数和析构函数。 -
构造函数(Constructor)
-
**作用:**构造函数的作用就是在对象被创建时利用特定的的值构造对象,将对象初始化为一个特定的状态。 -
一些特殊的性质
- 构造函数的函数名与类名相同,而且没有返回值;
- 构造函数通常被声明为公有函数
- 构造函数在对象被创建时将被自动调用
-
定义 class className{
public:
className(arg1,arg2,args3,...):var1(arg1),var2(arg2),var3(arg3){
[function body];
}
private:
[members];
}
如上代码中,var0,var1,var2…代表类中的数据成员,而 var1(arg1) 表示用用构造函数的形参 arg1 来给数据成员 var1初始化(赋初值)。 -
例, 虚数类的构造函数 class imaginary_number{
public:
imaginary_number(double real, double imaginary)
: real(real), imaginary(imaginary)
{
cout<<"cons."<<endl;
}
private:
double real;
double imaginary;
};
-
复制构造函数 复制构造函数是一种特殊的构造函数,其形参是本类的对象的引用。其作用是使用一个已经存在的对象(由复制构造函数的参数指定),去初始化同类的一个新对象。 若没有定义类的复制构造函数,系统会在必要时生成一个隐含的复制构造函数,其功能为:把初始值对象的每个数据成员的值复制到新建立的对象中。 -
复制构造函数,例 #include <iostream>
using namespace std;
class imaginary_number{
public:
imaginary_number(double real, double imaginary)
: real(real), imaginary(imaginary)
{
cout<<"cons."<<endl;
}
imaginary_number(const imaginary_number &imaginaryNumber){
this->real = imaginaryNumber.real;
this->imaginary = imaginaryNumber.imaginary;
cout<<"copied."<<endl;
}
friend std::ostream &operator<<(std::ostream &os, const imaginary_number &number) {
os <<number.real <<"+"<< number.imaginary<<"i";
return os;
}
private:
double real;
double imaginary;
};
int main(){
imaginary_number in1(1,2);
cout<<"origin: "<<in1<<endl;
imaginary_number *pn = new imaginary_number(in1);
cout<<"copy: "<<*pn<<endl;
delete pn;
return 0;
}
-
构造函数允许重载 Override 复制构造函数和普通构造函数互为重载函数。 在工程中,类中要有无参构造和有参构造, 无参构造和有参构造互为重载函数, 如下学生(Stu)类 #include <iostream>
using namespace std;
class Stu{
private:
public:
Stu(const string &name, const string &stuNum, double gpa) :
name(name), stuNum(stuNum), gpa(gpa) {
cout<<"stu cons."<<endl;
}
Stu() {
cout<<"stu cons without initial value"<<endl;
}
void setName(const string &name) {
Stu::name = name;
}
void setStuNum(const string &stuNum) {
Stu::stuNum = stuNum;
}
void setGpa(double gpa) {
Stu::gpa = gpa;
}
private:
string name;
string stuNum;
double gpa;
};
int main(){
Stu *pss[3];
for(int i=0;i<3;i++){
pss[i] = new Stu;
}
Stu s("ken","201",3.9);
for(int j=0;j<3;j++){
delete pss[j];
}
}
-
析构函数(Destructor)
-
作用 构函数与构造函数的作用几乎正好相反,它用来完成对象被删除前的一些清理工作。 -
一些性质
- 构函数是在对象的生存周期即将结束的时刻被自动调用的。
- 与构造函数一样,析构函数通常也是类的一个公有函数成员,它的名称是由类名前面加“~”构成,没有返回值;但与构造函数不同的是,析构函数不接受任何参数,不支持重载
- 如果不进行显式说明,系统会自动生成一个函数体为空的析构函数。
-
例子,类 Apple,其中的静态数据成员 apple_count 用来计数苹果的数量。 #include <iostream>
using namespace std;
class Apple{
public:
Apple(const string &kind) : kind(kind) {
apple_count++;
cout<<"apple "<<kind<<" cons."<<endl;
}
Apple(){
}
~Apple(){
apple_count--;
cout<<"apple "<<kind<<" des."<<endl;
}
static int getAppleCount();
private:
static int apple_count;
string kind;
};
int Apple::apple_count = 0;
int Apple::getAppleCount() {
return apple_count;
}
int main(){
Apple *pa1 = new Apple("花牛");
Apple *pa2 = new Apple("蛇果");
cout<<"有"<<pa2->getAppleCount()<<"种苹果"<<endl;
delete pa1;
cout<<"有"<<pa2->getAppleCount()<<"种苹果"<<endl;
delete pa2;
}
-
系统何时调用析构函数? class Samp1e{
public:
~Samp1e(){
cout<<"des."<<endl;
}
};
-
对象生命周期结束,被销毁时; int main(){
Samp1e s;
}
类Samp1e的对象 s 在 main 函数中是形参 形参的生命周期和所在函数的生命周期相同。因而main函数运行结束后,对象 s 自动销毁,调用析构函数。 这种情况还可能发生在 [对象作为函数形参,对象直接作为函数返回值(对象引用或者指向对象的指针作为返回值不调用析构函数)]这两种情况中,因为在上述两种情况中,系统会生成该对象的副本,当副本的生命周期结束时,调用析构函数。 #include <iostream>
using namespace std;
class Samp1e{
public:
~Samp1e(){
cout<<"Samp1e des."<<endl;
}
};
inline void fun(Samp1e s1){
return;
}
int main(){
Samp1e *ps = new Samp1e;
cout<<"对象作为函数参数:";
fun(*ps);
cout<<"[delete]指向对象指针:";
delete ps;
}
Samp1e *ps = new Samp1e;
inline Samp1e fun(){
return *ps;
}
int main(){
cout<<"对象作为函数返回值:";
fun();
cout<<"[delete]指向对象指针:";
delete ps;
}
-
delete指向对象的指针时; int main(){
Samp1e *ps = new Samp1e;
delete ps;
}
-
对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。 #include <iostream>
using namespace std;
class Samp1e{
public:
~Samp1e(){
cout<<"Samp1e des."<<endl;
}
};
class Samp2e{
public:
~Samp2e(){
cout<<"Samp2e des."<<endl;
}
private:
Samp1e s1;
};
int main(){
Samp2e s2;
}
|