面向对象程序设计与c++知识点整理

面向对象程序设计简介

面向对象和面向过程区别

面向过程设计缺点

1. 设计思路不够直接
2. 对数据没有保护
3. 代码重用性差

造成缺点的原因:

1. 数据和操作分离
2. 面向过程思维方式

面向对象三个重要概念

对象

构成系统的基本单位,描述客观事物的实体

属性:数据项

方法:操作序列

对象和对象之间只能靠函数调用或消息相互通信

类是具有共同特征的事物的抽象。

类是一种抽象数据类型,本身不占用内存

对象是类的实例,占用内存,有生存周期

消息

消息是向对象发出的服务请求

是对象间进行沟通的唯一手段

消息是由三部分组成:

1. 接受消息的对象
2. 消息选择符
3. 参数

面向对象三个基本特征

封装

将对象的属性和方法形成不可分割的整体

数据隐藏:只保留有限的对外接口,尽可能的隐藏对象内部实现的具体细节

优点:

1. 对对象属性的修改完全由自己负责,有效的避免了外部错误对于对象的影响。
2. 软件错误局部化
3. 减少差错,方便改错
4. 减少程序的工作量和负面影响

继承

在已有的类的基础上添加新的属性或功能而产生出新的类,称为继承

原有类称为基类,新类称为派生类

优点

1. 减少软件开发工作量
2. 大大提高代码重用律

多态

名称相同,实现过程不同的方法。
优点:

1. 用户不必知道对象属于哪个类就可以执行多态行为。
2. 为程序设计带来更大的方便

面向对象程序设计优点

1. 有完整的设计方法和理论
2. 可维护性高
3. 可拓展性高
4. 开发效率高
5. 软件的质量高

缺点:执行效率低

内存使用和函数调用

变量类型存储位置
非静态的局部变量
函数参数
程序执行代码代码段
全局变量
静态变量
数据段
文字常量文字常量段
malloc申请的空间

从c语言到c++语言

c++简介

字符集

大小写字母 A~Z, a~z

数字字符0~9

特殊字符

关键字

c++预定义的单词

标识符

程序员声明的单词

构成规则

以大写字母,小写字母,下划线开头
由大写字母,小写字母,下划线.数字组成
区分字母大小写

操作符

用于实现各种运算的符号

分隔符

{ } ( ) , ; :

用于分隔各个词法记号或程序正文

空白符

空格,制表符,换行符,注释

域作用域和名字空间

c++语言中,作用域分为函数原型作用域, 块作用域, 文件作用域, 类作用域

域作用符"::"

为了解决名字冲突的问题引入了名字空间的概念

引用某个名字空间当中的变量,可以使用名字空间加上域作用符引用

变量和函数的声明在头文件中
变量的定义和函数的实现在cpp文件中
extern关键字方便在实现文件定义变量

常量和常变量

常量

宏常量(define)

直接常量(“Hello World!”)

常变量

c++编译器在编译时将const数据作为常量,因此必须初始化.

int*类型的数据可以直接赋值给const int*类型,反之不可

指针常量和常量指针

const指针(int* const p)也可以称为指针常量

指向const数据的指针(const int * p)也可以称为常量指针

使用常量的好处
减少程序在编译时不经意修改数据,导致错误
增强程序可读性
对于一些情况,函数的参数和返回值不希望被改变,这时可以把他们声明为const

类型

基本数据类型

bool数据类型

void类型,枚举类型,数组,结构体,联合体

void类型

void类型三种用途:

用于声明函数无返回值
用于声明函数无参(可以省略)
用于声明void类型指针

任何类型指针都可以赋值给void指针,反之必须进行强制类型转换

函数

函数是对类功能的抽象

引用传递

定义一个引用时,必须初始化,初始化后无法修改

底层实现:

将&替换为* const
在定义是对于右值取地址
使用时在引用变量前加*

出现在声明变量的左面,表示引用
出现在声明变量的右面,表示取地址


不要返回局部变量的引用

什么时候使用引用
传递大量数据
函数返回一个内存空间作为左值

函数重载

c++中,函数是对类行为的描述,函数重载实质是允许函数同名.

编译器区分重载函数的方法

实参形参个数
实参形参类型

默认形参值

默认形参值必须按照从右到左顺序声明(形实结合顺序从左到右,避免产生歧义)

默认形参值应该在声明时候给出,实现时不要给出,如果两者都给出,编译失败

动态内存分配

new和delete的必要性

malloc和free无法满足对对象进行动态内存分配的需求

对于对象,需要在分配内存的时候构造,消亡时执行析构函数

new和delete可以在管理内存的时候进行类型检查,也可以在申请内存时候初始化

类和对象

c++中的类和对象

类的对象占用的大小由数据成员决定,与函数成员无关

访问控制

c++类中成员访问默认为私有的。

this指针

在成员函数中,this指针是隐含的,在参数列表中不可明确写出,但在程序中可以直接引用。

类函数的实现

封装性:对象有自己的属性,但外界想要访问,只能通过公有函数来访问,而不能随意存取。

类函数通常分为三大类:

存取函数
业务规则函数
构造函数和析构函数
实现语法
  1. 写在类的内部:和普通函数实现方式相同
  2. 写在类的外部,用类名和域作用符对函数名限定:

    返回值类型 类名::函数名(参数表)

    {

    函数实现

    }
  3. 在函数实现是可以直接访问数据成员,不在遵循先定义后使用的原则
默认形参值

在头文件中声明函数的时候给出默认形参值

在实现文件中不再定义默认值

const的使用

合理使用const可以提高程序的健壮性可读性

const的使用保证不能通过对函数返回的指针操作从而修改数据,

而只能通过调用公有函数修改数据成员

引用的使用

返回非const对象的引用可以对返回值做进一步处理(链式调用),可以提高程序的便捷性

类中的成员

构造函数

构造函数的作用主要是完成对对象的初始化。

构造函数的特点

函数的名字必须和类名相同

函数没有返回值

可以像其他函数一样被重载,但为了确保正确构造需要有至少一个公有重载形式

构造函数的几个注意点

构造函数不能有返回值,调用指令由编译器插入,不需要人为显式调用

如果定义类的时候没有声明任何构造函数,编译器会自动的为该类声明一个默认的构造函数,该构造函数不带任何参数且函数体为空

如果定义类的时候至少声明了一个构造函数,则编译器不再产生默认的构造函数。

析构函数

内存的分配与回收
分配回收
栈中的内存编译器管理编译器管理
数据段中的内容程序运行时分配结束前回收
堆中的内存程序员管理程序员管理
析构函数的特点

析构函数在对象消亡前自动调用

当没有给出显式的析构函数时,编译器会自动提供一个默认的析构函数,只不过析构函数什么事也不做

为保证编译器可以正确调用析构函数,必须声明为public等级

析构函数不可以显式调用

复制构造函数

函数传参三种方式:

  1. 值传递
  2. 指针传递
  3. 引用传递

其中复制构造函数不可以使用值传递,否则在形参和实参的结合过程中会调用复制构造函数,导致无限递归。

使用对象的引用传递方式的构造函数,由于功能常常是实现对象的复制,被称为复制构造函数

复制构造函数的注意点

如果没有给出复制构造函数,则编译器会自动生成一个复制构造函数,其实现的功能是按位复制,即对每一个数据成员进行复制,此时的复制方式是浅复制

调用复制构造函数的时机
  1. 用一个对象去初始化类的另一个对象
  2. 函数的形参是类的对象,当形参和实参相结合的时候
  3. 函数的返回值是类的对象,则在函数执行完返回调用者时

赋值运算符

赋值运算符的特点

如果没有重载赋值运算符,则编译器会提供一个默认的赋值运算符(只能实现浅复制)

组合类

一般地,B类的对象作为A类的数据成员时,称A类和B类间有“组合关系”(或组成关系),并称A类为组合类。

前向引用声明问题

定义新的类型是要遵从先声明后引用的规则

类的前向引用声明只能保证声明的符号可见,再给出类的具体定义前,不能涉及类的具体内容

不能再类中定义自身对象,只能定义指针

组合类的构造函数

c++规定按照对象在类的定义中出现的顺序来调用构造函数,最后调用组合类构造函数

如果构造函数没有在初始化列表中给出初始化方式,编译器会采用他默认的初始化方式,对于类的对象,会调用默认构造函数。

组合类的析构函数

析构函数调用方式和构造函数相反

组合类的复制构造函数

如果没有编写,编译器就会提供一个实现对应数据成员的复制(以初始化列表的形式)

如果给出了复制构造函数,则编译器不在提供。

内联函数

将程序功能划分封装好处
  1. 有利于代码重用
  2. 利于分工合作
  3. 增强可靠性
  4. 内存中只有一个拷贝

内联函数语法结构: inline 返回类型 函数名(参数表){函数体;}

内联函数的特点

inline关键字需要和函数体在一起,且应该在头文件中实现

类内部的函数自动成为内联函数

如果程序显式或隐式的引用了函数的地址,则编译器不执行内联功能

如果函数比较复杂,比如函数内有循环,switch语句,异常接口声明,编译器不执行内联功能

静态成员

单件类

类中可以定义自己的静态类型的对象,称为单件类

静态成员的作用
  1. 改变变量或函数的可见性
  2. 静态变量存储在数据段中,有文件生存期
  3. 静态变量是实现同类对象共享数据的一种方式
静态数据成员

在数据段中有独立存储区

使用sizeof计算对象大小不会算入静态数据成员

仅在类范围内有效

静态数据成员初始化方式:在实现文件中为静态变量分布内存并初始化(在类的外部初始化)

静态成员函数

静态成员函数在逻辑上存属于类,因此不会隐含的传递this指针,也就不能再函数中访问非静态数据成员或调用非静态成员函数

使用静态成员函数只能访问静态数据成员

对这类函数的调用不必通过某个类的对象

常成员和常对象

类中的数据成员必须声明为const,此时必须在构造函数的初始化列表中初始化他们

const对象:必须在声明的同时给对象初始化,并且在运行过程中不能改变对象的值

常对象只能调用常函数,不允许调用非常函数

任何不会修改数据成员的函数都应该声明为常函数,以方便const对象的使用

对象的生存期、作用域和可见性

对象类型作用域生存期可见性
全局变量文件作用域静态生存期文件内可见
局部变量块作用域动态生存期块内可见
静态局部变量块作用域静态生存期块内可见,外层不可见

类或对象之间的关系

类之间常见的关系

  1. 泛化关系(继承关系):描述了类之间是一个的关系
  2. 关联关系:有关系,但关系类型不确定
  3. 聚合关系:比较弱的一个有一个关系
  4. 组合关系:比较强的一个有一个关系(是…的一部分)

    对象整体不存在了,局部也就不存在了

友元类和友元函数

若B是A的友元类,则B可以访问A中受保护的和私有的数据成员

友元关系的声明语句不受访问控制关键字的约束

有时候仅需要类中的某个函数是本类的友元函数,则将该函数声明为友元函数
友元关系的特点

友元关系不可传递
友元关系单向
友元关系不可继承

运算符重载

运算符重载的一般形式

运算符重载的两种形式:

重载为类的成员函数

重载为类的外部函数(通常是友元函数)

重载运算符后,编译器会将源语句编译为以下语句

MyString str1("I love "), str2("C++!");
str1 = str1 + str2;
str1.opreator=(str2.operator+(str3));//重载为成员函数
operator=(str1,operator+(str2, str3));//重载为友元函数

通常,重载运算符为成员函数时,其参数列表的参数比实际需要参数数量少一个

重载为友元函数时,参数表中参数和实际参数一样多

例外:

前置++运算符和后置++运算符都是单目运算符,如果重载为成员函数应该如何区分?重载为友元函数如何区分?

//成员函数形式
X operator++();//前置++
X operator++(int);//后置++
//友元函数形式
X operator++(Y& a);//前置++
X operator++(Y& a, int);//后置++

特殊注意运算符

不可被重载的运算符:.,.*,::,sizeof, ?:

只能被重载为成员函数的运算符:=, [], ->, ()

只能被重载为友元函数的运算符:>>, <<

运算符的重载可以继承(赋值运算符除外)

编译器不会自动提供运算符重载(赋值运算符除外)

典型运算符重载

赋值运算符

赋值运算符可以将类的一个对象复制给类的另一个对象,并实现深复制的功能

当一个类的两端变量类型不同时,可以使用赋值运算符的另一种重载:转型赋值运算符

下标运算符

使用下标运算符就意味着他操作的是一个数组

返回的是其中的对象,且该对象可以方便的在赋值运算符左侧作为左值,因此常常返回对象的引用(还可以由常成员版本,返回const类型对象)

必须重载为非静态成员函数

函数调用运算符

函数调用运算符 () 可以被重载,使得对象实例可以被调用,就像调用函数一样。这允许类的实例具有类似函数的行为

自增自减运算符

假设已经给出前置自增运算符的实现,以大整数类为例,下面给出后置自增运算符的实现

CBigInt operator++(int)
{  CBigInt old(*this);
   this->operator++();
   return old;
}

自动类型转换

在c++中,如果一个表达式或函数调用使用了一个不合适的类型,他常常会执行类型转换

通过运算符重载方式来完成自动类型转换的方式是为类设计一个成员函数

  1. 该函数不带参数且函数名是要转换到的类型
  2. 该函数没有返回值
  3. 在函数名之前有关键字operator

转型构造函数:带有一个参数,且不为复制构造函数的构造函数

在需要类型转换时,编译器会自动寻找合适的转型构造函数尝试类型转换

为了防止转型构造函数执行隐式类型转换,可以将其定义为显式调用

explicit MyString(const char* p)

此时只允许显式调用,不允许隐式调用

流库类与输入输出

C++流库类结构

类别类名说明头文件
抽象流基类ios流基类ios
输入流类istream通用输入流类和其他输入流的基类istream
输入流类ifstream输入文件流类fstream
输入流类istringstream输入字符串流类sstream
输出流类ostream通用输出流类和其他输出流基类ostream
输出流类ofstream输出文件流类ostream
输出流类ostringstream输出字符串流类sstream
输入输出流iostream通用输入输出流和其他输入输出流iostream
输入输出流fstream输入输出文件流fstream
输入输出流stringstream输入输出字符串流sstream

标准输入输出函数

标准输入流:从标准输入设备提取数据流向内存变量

标准输出流:将数据从内存流中提取流向标准输出设备

预定义的标准输入流对象cin是istream对象

预定义的标准输出流对象cout, cerr,clog是ostream对象

cout对象在内存中开辟了一个缓冲区,当向流中插入endl时,立即输出流中所有数据,然后插入一个换行符并清空缓存

cerr对象的作用是向显示器输出错误信息,不能被重定向到磁盘文件,没有缓冲区,要输出的数据会立刻显示在磁盘上

clog对象有一个缓冲区,缓冲区满或遇到endl向显示器输出

文件和文件流

根据数据的存储方式,文件分为文本文件二进制文件:

文本文件:基于字符编码,文件中保存各个字符的字符编码

二进制文件:字符信息依然是以ASCII存储,但数值信息存储方式不同(二进制)

输入文件流

标志含义
ios_base::in以默认方式打开文件
ios_base::binary以二进制方式打开文件

在使用二进制写入的时候,一定要以二进制方式打开文件,否则会出现意想不到的错误

读写文件样例

MyString定义:

class MyString
{
private:
    char* m_pbuf;
    ......
public:

}

文本文件读写

void write_text(ostream& o) const
{
    int len = strlen(m_pbuf);
    o << len << ' ' << m_pbuf;
}
MyString& read_text(istream& in)
{
    delete [] m_pbuf;
    int len;
    in >> len;
    m_pbuf = new char[len + 1];
    in.read(m_pbuf, len);
    m_pbuf[len] = '\0';
    return *this;
}

MyString str("I Love C++ Programming!");
ofstream out("text.txt");
str.write_text(out);
out.close();
ifstream in("text.txt");
str.read_text(in);
in.close();

二进制文件读写

void write_binary(ostream& o) const
{
    int len = strlen(m_pbuf);
    o.write((char*)&len, sizeof(int));
    o.write(m_pbuf, len);
}
MyString& read_binary(istream& in)
{
    delete [] m_pbuf;
    int len;
    in.read((char*)&len, sizeof(int));
    m_pbuf = new char[len + 1];
    in.read(m_pbuf, len);
    m_pbuf[len] = '\0';
    return *this;
}
MyString str("I Love C++ Programming!");
ofstream out("text.dat", ios_base::binary);
str.write_binary(out);
out.close();
ifstream in("text.dat", ios_base::binary);
str.read_binary(in);
in.close();

继承和组合

继承的含义

问题域中的事物往往属于不同层次,高层抽象的事物具有的特征再底层具体事物上都会体现,这个叫做继承

继承是实现代码重用的重要方式之一

当一个类的功能都不能满足新的要求,但该类的功能都可以为我所用,使用继承机制从该类派生出新的类

可以重复使用原有的、可靠的代码,减少工作量、增加工作效率,程序的演化也更清楚,更容易控制

继承方式

基类:被继承的类

派生类:通过继承方式产生的新类

c++中继承方式有三种:

  1. 公有继承
  2. 私有继承
  3. 保护继承

c++中允许一个类有多个基类,称为多继承

如果没有给出继承方式,默认是私有继承

派生类的数据成员是指除了从基类中继承的数据成员,新增加的数据成员和函数,以及在基类中存在但在派生类中实现方式不同的函数成员

继承方式主要影响派生类中成员的访问控制

基类成员再派生类中的访问属性
基类成员派生类成员
公有继承保护继承私有继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可访问不可访问不可访问
不可访问不可访问不可访问不可访问

派生类中成员

派生类中的成员包括:

  1. 从基类继承下来的成员
  2. 自己新增的数据成员

各司其职原则

A类该完成的事情就不要再B类中在实现,可以直接调用A类的实现

在派生类中调用基类的函数需要使用基类名加域作用符加以限定

同名隐藏

如果派生类中实现了和基类相同的函数,则基类无法调用派生类的函数

在派生类中找不到合适的函数也不会去基类中寻找

基类和派生类关系

派生类可以作为基类使用,反之不行

基类对象和派生类对象在内存中有包含关系,派生类对象的一部分内存是基类对象的,另一部分是派生类中新增数据成员需要的

基类指针可以指向派生类,反之不行

构造函数

构造派生类对象需要调用派生类构造函数,同时派生类对象也是基类对象,所以也要调用基类构造函数

构造函数一般实现形式如下:

派生类名::派生类名(参数表):基类名(参数表),内嵌对象1(参数表),内嵌对象2(参数表),...
{
    实现语句;
}

调用派生类构造函数发生相关调用的顺序:

  1. 基类的构造函数
  2. 内嵌对象的构造函数
  3. 派生类的构造函数

如果有基类的默认构造函数,派生类可以不明确调用基类的构造函数,否则不可以不明确调用基类的构造函数

析构函数

设计析构函数也要遵循各司其职的原则

如果派生类在消亡前需要回收堆内存,同时又使用了基类指针指向派生类对象,那么此时通过基类指针删除派生类对象就会发生内存泄漏

原因:此时只调用了基类的析构函数,没有析构派生类的成员

复制构造函数

当没有编写复制构造函数时,编译器会自动生成一个默认的复制构造函数

对于派生类来说,该复制构造函数会调用基类的复制构造函数

多继承和虚基类

在调用派生类的构造函数时,会自动调用基类的构造函数,调用顺序时在声明基类的时候各个基类的排列顺序

在多继承中,常常会发生子对象重叠的问题,解决方案时将基类声明为虚基类

多态和虚函数

静态绑定和动态绑定

绑定:是把函数体和函数调用联系在一起的过程

静态绑定

函数调用与函数体的关系在运行前就已经确定为静态绑定

动态绑定

程序运行时根据当前的实际情况决定调用哪个函数实现,即函数调用和函数体的关系在运行时才能确定,称之为动态绑定

在c++中,实现动态绑定的机制时虚函数

注意

并不是对所有的虚函数都执行动态绑定

如果可以事先确定到底调用哪个函数的实现,执行静态绑定

使用成员函数,指针,引用,编译器无法判断调用哪个函数的实现,采用动态绑定

虚函数

对于类的一个函数,为了执行动态绑定,c++要求在基类声明函数的时候使用virtual关键字

注意:

  1. virtual关键字只能在函数声明的时候使用,函数实现不能再重复使用
  2. 基类中声明的虚函数在派生类中自动成为虚函数,不必再次使用virtual关键字

判断派生类中某一个函数是否是虚函数的方法:

  1. 是否声明virtual关键字
  2. 函数名,参数表,返回值类型是否和基类某个虚函数完全一样

构造函数和析构函数

对于含有虚函数的类,在执行构造函数的时候,不仅要初始化数据成员,还要初始化虚指针,使他指向类的虚表

对虚指针的初始化是由编译器添加到构造函数中的

在构造函数中调用虚函数只能执行本地的函数,实行静态绑定

在析构函数中调用函数只能执行本地的函数,实行静态绑定

注意:
如果派生类中需要修改基类的函数,则可以将该函数声明为虚函数

对于基类中的非虚函数,通常代表不希望被派生类改变的功能,不需要重写这些函数

默认形参值执行静态绑定

动态类型转换

派生类对象可以当作基类对象来使用

基类指针可以指向派生类对象

dynamic_cast转换方式

dynamic_cast<目标指针或引用类型>(源指针或原类型)

要求:

目标类和和原类之间有多态类继承关系
有继承关系且基类中有虚函数
如果转换成功返回有效指针,否则返回空指针

纯虚函数和抽象类

纯虚函数

纯虚函数:仅需要提供函数声明,不需要为函数提供实现

语法格式:
virtual 返回值类型 函数名(参数表) = 0;

一个函数被声明为虚函数,则不能再为该函数提供函数体。

在以该类为基类的派生类中,如果需要,可以为其提供函数体

抽象类

抽象类:带有纯虚函数的类

主要作用是为类族提供公共接口,使其更好发挥多态性

如果一个派生类类中还存在虚函数,他就是抽象类
当所有虚函数都给出实现,派生类就不是抽象类

抽象类不可以创建对象

模板与STL

模板

函数模板

c++提供的模板机制可以实现数据类型参数化

定义模板函数的语法格式:

template <class T1, class T2, …> 返回类型 函数名(参数表)

{

函数体

}

根据函数模板,编译器会自动根据调用情况生成一个函数,称为模板函数

调用模板函数是,注意参数类型必须要和规定类型一致

编译器不执行隐式的类型转换

类模板

使用类模板可以为类声明一种模式,参数化其中某些数据类型

类模板声明语法格式:

template<class T1, class T2, …> class 类名

{

类成员声明

}

如果在类外部声明类的成员函数:

template<class T1, class T2, …> 返回值类型 函数名(参数表)

{

函数体

}

类模板自身不产生代码,只是说明了一个类族,只有当引用类模板的时候,才会产生所需要的类

STL简介

C++标准模板库(standard template library, STL)是泛型程序设计(generic programming, GP)的典范

STL以极高的抽象程序,通用性和极为出色的运行效率,实现了c++程序设计普遍涉及到的一大批数据结构和算法,成为了c++标准语言库的一个重要组成部分

STL内容主要包括:

  1. 容器
  2. 迭代器
  3. 泛型算法
  4. 函数对象
  5. 适配器
  6. allocator内存分配系统
  • 39
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值