c++面向对象程序设计课程笔记(简单易于理解)

目录

  • 第一章
  • 1. 概念性的名词:
  • 第二章
    • 2.1 命名空间
    • 2.2 define 和 const的差异
    • 2.3 函数原型c与c++的区别
    • 2.4 基本数据类型
      • c++中的bool类型
      • 运算优先级、类型转换
    • 2.5 引用
    • 2.6 内联函数
    • 2.7 带默认参数值的函数
        • 默认参数值设定
        • 默认参数值的说明次序
        • 默认参数值与函数的调用位置
    • 2.8 函数重载
        • 函数重载的概念
    • 2.9 new 和 delete 动态分配内存空间
        • 分配和释放动态数组
        • new ,delete 和 malloc free 的异同
  • 第三章
    • 3.1类与对象的定义
        • 类的语法定义
        • 类内初始值
        • 成员函数的定义
        • 类成员的访问控制
    • 3.2 对象的语法定义
    • 3.3 构造函数基本概念
        • 构造函数的作用
        • 构造函数的形式
        • 构造函数的调用方式
        • 默认构造函数
        • 隐含生成的构造函数
    • 3.4 拷贝构造函数
        • 拷贝构造函数的定义
    • 3.5 析构函数
    • 3.6 类与结构体
        • 结构体的基本概念
        • 两者相同点
        • 两者差别点
    • 3.7 对象数组和对象指针
        • 对象数组
        • 对象指针
          • this 指针
    • 3.8类的静态数据成员
        • 静态数据成员
        • 类的静态函数成员
    • 3.9 类的友元
        • 友元函数
        • 友元类
        • 类的友元关系是单向的
    • 3.10常类型
        • 常对象
        • 常成员
    • 3.11 string 类
  • 第四章
    • 4.1 继承与派生
        • 概述
        • 目的
    • 4.2单继承时派生类的定义
    • 4.3 多继承时派生类的定义
    • 4.4 继承方式
    • 4.5 派生类的构造函数
    • 4.6 同名成员
    • 4.7 虚基类
        • 虚基类构造函数的调用顺序
  • 第五章
    • 5.1 多态性
      • 运算符重载
      • 双目运算符重载规则
      • 前置单目运算符重载规则
      • 后置单目运算符 ++和--重载规则
  • 第八章
  • 8. 容器
    • 8.1 vector (向量/动态数组)
      • `vector 定义`:
      • ` vector 遍历`:`:
      • vector中常用函数
    • 8.2 map(映射)
      • 8.2.1如何理解map中的映射:
      • 8.2.2如何定义map:
      • 8.2.3map的访问:
      • 8.2.4map常用函数:
      • 8.2.5常见用途:
    • 8.3set(集合)
        • 8.3.1set的定义
        • 8.3.2set内元素的访问
        • 8.3.3set常用函数:
    • 8.4 stack
      • 8.4.1 stack 的定义
      • 8.4.2 stack 容器内元素的访问
      • 8.4.3stack常用函数:
    • 8.5 queue队列
      • 8.5.1 queue 的定义
      • 8.5.2 queue 容器内元素的访问
      • 8.5.3queue常用函数:

第一章

1. 概念性的名词:

对象:

  • 一般意义上的对象:现实世界中实际存在的事物。
  • 面向对象方法中的对象:程序中用来描述客观事物的实体。

抽象与分类

  • 将有关事物共性归纳、集中的过程,即分类依据的原则——抽象;
  • 抽象出同一类对象的共同属性和行为形成类(“类”是一组具有相同属性和行为的对象的抽象);
  • 类与对象是类型与实例的关系,抽象和具体的关系
例如:抽象实例——钟表
// 数据抽象:
int hour,int minute,int second
//代码抽象:
setTime(),showTime()

class Clock {
public:
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;};

封装

  • 把数据和实现操作的代码集中起来放在对象内部,隐蔽对象的内部细节;
  • 对外形成一个边界;
  • 只保留有限的对外接口;
  • 使用方便、安全性好。
  • 实现封装:类声明中的 {}
例:
class Clock {
public: void setTime(int newH, int newM, int newS);
void showTime();
private: int hour, minute, second;
};

继承

  • 意义在于软件复用;
  • 改造、扩展已有类形成新的类。
  • 若类之间有继承关系,则它们之间具有下列几个特性
    1. 类间具有共享性(包括数据和操作代码的共享)
    2. 类间具有差别或者新增部分(可以加入自己所特有的新特性)
    3. 类间有层次结构(直接基类,间接基类,a->b->c)
  • 多继承和单继承
    1. 单继承:每个派生类只直接继承了一个基类的特征
    2. 多继承:多个基类派生出一个派生类(基类-父类,派生类-子类)

多态

  • 同样的消息作用在不同对象上,可以引起不同的行为。同一名称,不同的功能实现方式。
  • 目的:达到行为标识统一,减少程序中标识符的个数。
  • 实现:重载函数和虚函数

第二章

2.1 命名空间

避免命名冲突
std 是 C++标准库的命名空间( namespace)名
using namespace std 表示打开 std 命名空间

2.2 define 和 const的差异

  1. 常量定义,const 与define
    const 可以定义常量的类型 例如:const int a = 100;
    define 可以定义语句或片段 例如:#define ADD(a, b) ((a)+(b)) 必须要加括号,防止宏定义中部分替换而导致的运行错误。
  2. const 与指针的使用
    指向常量的指针: 不能改变常量的值,但是可以改变指针的地址
const char * name = "change" ;
// 可以理解为,const *name ,其中*name 是一个普通常量,因此常量的值是不能改变的
因此:name[3] = 'a' 是不可法的
name = "zhang" 是合法的,相当于 string t = "zhang" , name = &t;//这个时候仅仅只是改变了name指针变量所指向的地址。

常指针:不能改变指针的地址,但是可以改变指针所指向常量的值

char * const name = "chen";
name[3] = 'a';  //合法
name = "zhang" //不合法

指向常量的长指针:不能改变指针的地址,也不能改变指针所指向常量的值

tip: name 指针变量,表示地址。不好理解的话,可以想到数组,数组就是一个特殊的指针,int a[10] ,其中a,就是表示数组a[0]的地址。
如果还是不能理解的话可以看看c语言中指针的知识点:
这是本人写的相关博客连接:c语言重难点笔记

2.3 函数原型c与c++的区别

函数原型格式:返回类型名 函数名 (参数表); //注意这个分号
c 与 c++ 的区别在于:

  • 在c中,可以对函数原型进行简化:
    可以省略参数表,返回类型名。
  • c++中,则不能省略
    其主要目的:让c++编译程序进行检查,已确定调用函数的参数以及返回值类型与事先定义的是否相同。
    如果没有注明参数表,则说明其为void
    c++中,main函数一定要为int 类型

2.4 基本数据类型

c++中的bool类型

  • 只有两个值:true(真) 、false(假)
  • 常用来表示关系比较、相等比较或逻辑运算的结果
  • 所有⾮零值解释为 true ,零值解释为 false
  • 直接赋值⼀个数字给 bool 变量它会⾃动根据 int 值是不是零来决定
    给 bool 变量赋值 true 还是 false

c++面向对象程序设计课程笔记(简单易于理解)_第1张图片

运算优先级、类型转换

c++面向对象程序设计课程笔记(简单易于理解)_第2张图片

2.5 引用

引用(&)是标识符的别名

  • 定义一个引用时,必须同时对它进行初始化,使它指向一个已存在的对象。
int i, j;
int &ri = i; //定义 int 引用 ri,并初始化为变量 i 的引用
j = 10;
ri = j; //相当于 i = j;
  • 一旦一个引用被初始化后,就不能改为指向其它对象。
  • 变量 i 和引用 j 占用内存的同一位置,i 和 j 同时跟新
    指针与引用的差别:
    指针不同在于其仅仅只是指向了 i 的地址,只要 i 的地址不改变,i的值发生改变,那么*j的值也会跟着发生改变。
  • 引用可以作为形参
#include
using namespace std;
void swap1(int& a, int& b) {
int t = a;
a = b;
b = t;
}
void swap2(int a, int b) {
int t = a;
a = b;
b = t;
}
int swap3(int a, int b) {
int t = a;
a = b;
b = t;
return a,b;
}
int main() {
int x = 5, y = 10;
cout<<"x = "<<x<<" y = "<<y<<endl;
swap1(x, y);
cout<<"x = "<<x<<" y = "<<y<< endl;   //可以直接改变x,y的值
swap2(x, y);  //由于没有返回值,形参的改变不会影响实参
x , y = swap3(x,y) //这样才可以改变
return 0;

2.6 内联函数

motivation: 在函数进行调用时,系统会将程序当前的一些状态信息(例如现场和返回地址等)存到栈中,同时转到函数的代码处去执行函数体语句,这些参数保存与传递的过程需要时间和空间的开销,这样就会降低程序执行的效率。为了消除函数调用时的系统开销,以提高运行效率。简单的说:编译时在调用处用函数体进行替换,节省了参数传递、控制转移等开销,用空间换取时间的策略。

  • 声明时使用关键字 inline。
    注意:
  • 内联函数体内不能有循环语句和 switch 语句;
  • 内联函数的定义必须出现在内联函数第一次被调用之前;
  • 对内联函数不能进行异常接口声明。
  • 通常是规模小(代码行数少),使用频繁的函数才定义为内联函数
#include 
using namespace std;
const double PI = 3.14159265358979;
inline double calArea(double radius) {
return PI * radius * radius;
}
int main() {
double r = 3.0;
double area = calArea(r);
cout << area << endl;
return 0;
}

2.7 带默认参数值的函数

默认参数值设定

  • 可以预先设置默认的参数值,调用时如给出实参,则采用实参值,否则采用预先设置
int add(int x = 5,int y = 6) {
return x + y;
}
int main() {
add(10,20); //10+20
add(10); //10+6
add(); //5+6
}

默认参数值的说明次序

  • 有默认参数的形参必须列在形参列表的最右,即默认参数值的右面不能有无默认值的参数;
  • 调用时实参与形参的结合次序是从左向右。
int add(int x, int y = 5, int z = 6);//正确
int add(int x = 1, int y = 5, int z);//错误
int add(int x = 1, int y, int z = 6);//错误

默认参数值与函数的调用位置

  • 如果一个函数有原型声明,且原型声明在定义之前,则默认参数值应在函数原型声明中给出;如果只有函数的定义,或函数定义在前,则默认参数值可以函数定义中给出。
//原型声明在前
int add(int x = 5,int y = 6);
int main() {
add();
}
int add(int x,int y) {
//此处不能再指定默认值
return x + y;
}


//只有定义,没有原型声明
int add(int x = 5,int y = 6) {
return x + y;
}
int main() {
add();
}

2.8 函数重载

函数重载的概念

motivation: 解决c语言中,函数名必须唯一的不便之处。
C++允许功能相近的函数在相同的作用域内以相同函数名声明,从而形成重载。方便使用,便于记忆。

  • 重载的条件
  1. 可以是形参类型不同
int add (int x ,int y);
float add(float x , float y);
  1. 形参个数不同
int add (int x , int y) ;
int add(int x , int y, int z);
  1. 1和2都满足
    注意:
int add (int x, int y); int add (int a , int b) ; 
/ /错误!编译器不以形参名来区分函数
int add (int x ,inty); void add(int x , int y); 
//错误!编译器不以返回值来区分函数

例题:

编写两个名为 sumOfSquare 的重载函数,分别求两整数的平方和及两实数的平方和。
#include 
using namespace std;
int sumOfSquare(int a, int b) {
return a * a + b * b;
}
double sumOfSquare(double a, double b) {
return a * a + b * b;
}
int main() {
int m, n;cout << "Enter two integer: ";
cin >> m >> n;
cout<<"Their sum of square: "<<sumOfSquare(m, n)<<endl;
double x, y;
cout << "Enter two real number: ";
cin >> x >> y;
cout<<"Their sum of square: "<<sumOfSquare(x, y)<<endl;
return 0;
}
 运行结果:
Enter two integer: 3 5
Their sum of square: 34
Enter two real number: 2.3 5.8
Their sum of square: 38.93

2.9 new 和 delete 动态分配内存空间

运算符new用于内存分配的最基本形式为:
指针变量名 = new 类型;

原理:从堆的一块自由存储区中为程序分配一块与类型字节数相适应的内存空间,并将该块内存的首地址存于指针变量。
int *p;
p = new int; //动态分配一个整型存储空间,并将首地址赋值
……
delete p; //释放指针p指向的存储空间

 //动态创建对象举例
#include 
using namespace std;
class Point {
	public:
		Point() : x(0), y(0) {
			cout<<"Default Constructor called."<<endl;
		}
		Point(int x, int y) : x(x), y(y) {
			cout<< "Constructor called."<<endl;
		}
		~Point() { cout<<"Destructor called."<<endl; }
		int getX() const { return x; }
		int getY() const { return y; }
		void move(int newX, int newY) {
			x = newX;
			y = newY;
			}
		private:
			int x, y;
};
int main() {
	cout << "Step one: " << endl;
	Point *ptr1 = new Point; //调用默认构造函数
	delete ptr1; //删除对象,自动调用析构函数
	cout << "Step two: " << endl;
	ptr1 = new Point(1,2);
	delete ptr1;
	return 0;
}
运行结果:
Step One:
Default Constructor called.
Destructor called.
Step Two:
Constructor called.
Destructor called.

分配和释放动态数组

  • 分配:new 类型名T [ 数组长度 ]
  • 数组长度可以是任何表达式,在运行时计算 释放:delete[] 数组名p
  • 释放指针p所指向的数组。
    p必须是用new分配得到的数组首地址。
动态创建对象数组举例
#include
using namespace std;
class Point {
	public:
		Point() : x(0), y(0) {
			cout<<"Default Constructor called."<<endl;
		}
		Point(int x, int y) : x(x), y(y) {
			cout<< "Constructor called."<<endl;
		}
		~Point() { cout<<"Destructor called."<<endl; }
		int getX() const { return x; }
		int getY() const { return y; }
		void move(int newX, int newY) {
			x = newX;
			y = newY;
			}
		private:
			int x, y;
};
int main() {
	Point *ptr = new Point[2]; //创建对象数组
	ptr[0].move(5, 10); //通过指针访问数组元素的成员
	ptr[1].move(15, 20); //通过指针访问数组元素的成员
	cout << "Deleting..." << endl;
	delete[] ptr; //删除整个对象数组
	return 0;
}
运行结果:
Default Constructor called.
Default Constructor called.
Deleting...
Destructor called.
Destructor called.

new ,delete 和 malloc free 的异同

int *pArr = (int *)malloc(sizeof(int)*len);
//表示我们的操作系统,要为我们的程序分配len个存储空间,可以进行读写
// malloc函数只返回第一个字节的地址,这样无法区分数组的类型,干地址
//因此需要用一个强制类型转换
相同点
都是用于内存的动态分配
差异

  • new 不需要向malloc 那样进行强制类型转换,new可以自动返回正确的指针类型
  • new 可以根据数据类型自动计算所要分配内存的大小,不需要使用sizeof函数来计算所需要的字节数。

第三章

3.1类与对象的定义

类的语法定义

class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护型成员
}

类内初始值

  • 可以为数据成员提供一个类内初始值
  • 在创建对象时,类内初始值用于初始化数据成员
  • 没有初始值的成员将被默认初始化。
class Clock {
public:
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour = 0, minute = 0, second = 0;
}

成员函数的定义

  • 方式一:
    • 在类声明中只给出成员函数的原型,而将成员函数的定义放在类的外部。
成员函数在类外的定义:
返回值类型 类名::成员函数名(参数表){
	函数体
}

“::” 作用域运算符,在类名和函数名之间加上作用域运算符“::” ,用于声明这个成员函数时属于哪几类的;在局部变量的作用域内使用使用同名的全局变量,可以使用 “::",来区分局部变量和全局变量,::变量名 表示全局变量。

这种方式有利于减少类体的长度,增加类的可读性,将类的接口与类的实现细节相分离,隐藏执行细节。

类成员的访问控制

数据成员和成员函数一般统称为类的成员。

  • 公有类型成员
    • 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
  • 私有类型成员
    • 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
    • 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。
  • 保护类型成员
    • 与private类似,其差别表现在继承与派生时对派生类的影响不同

注意:在默认情况下(即没有指定属于私有或共有时),类中的成员是私有的。因此一个类中没有申明成员类型,则成员都为私有类型成员,则类外对象不能访问类内成员

3.2 对象的语法定义

  • 类名 对象名;
    例:Clock myClock;
  • 类成员的访问权限
    • 类中成员互相访问
      直接使用成员名访问
    • 类外访问
      使用“对象名.成员名”方式访问 public 属性的成员。
例 钟表类
类的定义
#include
using namespace std;
class Clock{
public:
void setTime(int newH = 0, int newM = 0, int newS = 0);
void showTime();private:
int hour, minute, second;
}
成员函数的实现
void Clock::setTime(int newH, int newM, int newS) {
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime() {
cout << hour << ":" << minute << ":" << second;
}
对象的使用
int main() {
Clock myClock;
myClock.setTime(8, 30, 30);
myClock.showTime();
return 0;
}

3.3 构造函数基本概念

构造函数的作用

  • 在对象被创建时使用特定的值构造对象,将对象初始化为一个特定的初始状态。
    例如:希望在构造一个Clock类对象时,将初试时间设为0:0:0,就可以通过构造函数来
    设置。

构造函数的形式

函数名与类名相同;
不能定义返回值类型,也不能有return语句;
可以有形式参数,也可以没有形式参数;
可以是内联函数;
可以重载;
可以带默认参数值。

构造函数的调用方式

  • 在对象创建时被自动调用
    例如:Clock myClock(0,0,0);

默认构造函数

  • 调用时可以不需要实参的构造函数
    参数表为空的构造函数
    全部参数都有默认值的构造函数
  • 下面两个都是默认构造函数,如在类中同时出现,将产生编译错误:
    Clock();
    Clock(int newH=0,int newM=0,int newS=0);

隐含生成的构造函数

  • 如果程序中未定义构造函数,编译器将在需要时自动生成一个默认构造函数
    • 参数列表为空,不为数据成员设置初始值;
    • 如果类内定义了成员的初始值,则使用内类定义的初始值;
    • 如果没有定义类内的初始值,则以默认方式初始化;
    • 基本类型的数据默认初始化的值是不确定的。
//构造函数例题:
class Clock {
public:
Clock(int newH,int newM,int newS);//构造函数
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
//构造函数的实现:
Clock::Clock(int newH,int newM,int newS): hour(newH),minute(newM),
second(newS) {
}
int main() {
Clock c(0,0,0); //自动调用构造函数
c.showTime();
return 0;
}
//构造函数例题二
class Clock {
public:
Clock(int newH, int newM, int newS); //构造函数
Clock(); //默认构造函数void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
Clock::Clock(): hour(0),minute(0),second(0){ }//默认构造函数
//其它函数实现同前
int main() {
Clock c1(0, 0, 0); //调用有参数的构造函数
Clock c2; //调用无参数的构造函数
……
}

3.4 拷贝构造函数

拷贝构造函数的定义

motivation
复制构造函数是一种特殊的构造函数,其形参为本类的对象引用。作用是用一个已
存在的对象去初始化同类型的新对象。

class 类名 {
public :
类名(形参);//构造函数
类名(const 类名 &对象名);//拷贝构造函数
// ...
};
类名::类( const 类名 &对象名)//拷贝构造函数的实现 采用引用的方式进行赋值
{ 函数体 }
例  Point 类的完整程序
class Point { //Point 类的定义
public:
Point(int xx=0, int yy=0) { x = xx; y = yy; } //构造函数,内联
Point(const Point& p); //复制构造函数
void setX(int xx) {x=xx;}
void setY(int yy) {y=yy;}
int getX() const { return x; } //常函数
int getY() const { return y; } //常函数
private:
int x, y; //私有数据
};
//拷贝构造函数的实现
Point::Point (const Point& p) {
x = p.x;
y = p.y;
cout << "Calling the copy constructor " << endl;
}
//形参为Point类对象void fun1(Point p) {
cout << p.getX() << endl;
}
//返回值为Point类对象Point fun2() {
Point a(1, 2);
return a;}
int main() {
Point a(4, 5);
Point b(a); //用a初始化b。
cout << b.getX() << endl;
fun1(b); //对象b作为fun1的实参
b = fun2(); //函数的返回值是类对象
cout << b.getX() << endl;
return 0;
}

3.5 析构函数

  • 完成对象被删除前的一些清理工作。
  • 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。
  • 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数,其函数体为
    空。
构造函数和析构函数举例
#include 
using namespace std;
class Point {
public:
Point(int xx,int yy);
~Point();
//...其他函数原型
private:
int x, y;
};

3.6 类与结构体

• 为什么会出现结构体:为了表示一些复杂的数据,而普通的基本类型变量无法满足要求;
• 定义:结构体是用户根据实际需要自己定义的复合数类型;

结构体的基本概念

//定义结构体
#include 
struct Student
{
    int sid;
    char name[200];
    int age;
};

struct Student st = {1001,"zhangsan",18}//整体赋值,类似于Java中new类的构造函数 
st.id=1001//单个赋值
strcpy(st.name,"zhangsan");
st.age=18struct Student *pst = &st;//通常使用指针的方式赋值
//pst所指向的结构体变量中的sid这个成员
pst->sid=1001//==(*pst).sid==st.sid
strcpy(pst->name,"lisi");
pst->age=19

结构体在定义一个对象后,系统为自定进行初始化,数值类型为0,字符类型为空。
注意事项 结构体变量不能算术计算,但是可以赋值;

两者相同点

  • 结构体和类,其数据的默认值时相同的,对于数值型为0,对于字符型为空。其原理都是采用了默认构造函数,因此结构一也是可以用构造函数进行赋值的
typedef struct person{
	char name[6];
	int yy,mm,dd;
	person(){}
	person(int _yy,int _mm,int _dd){
		yy = _yy;
		mm = _mm;
		dd = _dd;
	}
}PER;

具体应用可以参考本人写的结构体应用博客:结构体应用解析

两者差别点

  • 类是结构体的扩充,类不仅可以含有不同类型的数据,而且还可以有函数(操作行为);结构体仅仅只能表示各种不同类型的数据。
  • 类默认情况下是成员为私有类型成员,结构体默认情况下是公有类型成员

3.7 对象数组和对象指针

对象数组

对象数组的定义与访问

  • 定义对象数组
    类名 数组名[元素个数];
  • 访问对象数组元素
    通过下标访问,数组名[下标].成员名

对象类数组初始化:

  • 数组中每个元素对象被创建时,系统都会调用类构造函数初始化该对象。
  • 通过初始化列表赋值,如:
    Point a[2] = {Point(1,2),Point(3,4)};
  • 如果没有为数组元素制定显示初始值,数组元素边使用默认值初始化

数组元素所属类的构造函数

  • 元素所属的类不声明构造函数,则采用默认构造函数。
  • 各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。
  • 各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。
  • 当数组中每一个对象被删除时,系统都要调用一次析构函数。
#include 
using namespace std;
class Point { //类的定义
	public: //外部接口
		Point();
		Point(int x, int y);
		~Point();
		void move(int newX,int newY);
		int getX() const { return x; }
		int getY() const { return y; }
		static void showCount(); //静态函数成员
	private: //私有数据成员	
		int x, y;
};
Point::Point() : x(0), y(0) {
	cout << "Default Constructor called." << endl;
}
Point::Point(int x, int y) : x(x), y(y) {
	cout << "Constructor called." << endl;
}
Point::~Point() {
	cout << "Destructor called." << endl;
}
void Point::move(int newX,int newY) {
	cout << "Moving the point to (" << newX << ", " << newY << ")" << endl;
	x = newX;
	y = newY;
}
int main() {
	cout << "Entering main..." << endl;
	Point a[2];
	for(int i = 0; i < 2; i++)
	a[i].move(i + 10, i + 20);
	cout << "Exiting main..." << endl;
	return 0;
}

对象指针

  • 对象指针定义形式
    类名 *对象指针名;
    Point a(5,10);
    Piont *ptr;
    ptr=&a;
  • 通过指针访问对象成员
    对象指针名->成员名
    例:ptr->getx() 相当于 (*ptr).getx();
//使用指针来访问 Point 类的成员
#include 
using namespace std;
class Point {
	public:
		Point(int x = 0, int y = 0) : x(x), y(y) { }
		int getX() const { return x; }
		int getY() const { return y; }
	private:
		int x, y;
};
int main() {
	Point a(4, 5);
	Point *p1 = &a; //定义对象指针,用a的地址初始化
	cout << p1->getX() << endl;//用指针访问对象成员
	cout << a.getX() << endl; //用对象名访问对象成员
	return 0;
}
this 指针
  • 指向当前对象自己
  • 隐含于类的每一个非静态成员函数中。
  • 指出成员函数所操作的对象。
  • 当通过一个对象调用成员函数时,系统先将该对象的地址赋给this指针,然后调
    用成员函数,成员函数对对象的数据成员进行操作时,就隐含使用了this指针
  • 例如:Point类的getX函数中的语句:
    return x; 相当于: return this->x;

3.8类的静态数据成员

静态数据成员

  • 用关键字static声明
    • 为该类的所有对象共享,静态数据成员具有静态生存期。
    • 必须在类外定义和初始化,用(::)来指明所属的类。
#include 
using namespace std;
class Point { //Point类定义
public: //外部接口
	Point(int x = 0, int y = 0) : x(x), y(y) { //构造函数
	//在构造函数中对count累加,所有对象共同维护同一个count
	count++;
	}
	Point(Point &p) { //拷贝构造函数
		x = p.x;
		y = p.y;count++;
	}
	~Point() { count--; }
	int getX() { return x; }
	int getY() { return y; }
	void showCount() { //输出静态数据成员
	cout << " Object count = " << count << endl;
	}
	private: //私有数据成员
		int x, y;
		static int count; //静态数据成员声明,用于记录点的个数
};
int Point::count = 0;//静态数据成员定义和初始化,使用类名限定
int main() { //主函数
	Point a(4, 5); //定义对象a,其构造函数回使count增1
	cout << "Point A: " << a.getX() << ", " << a.getY();
	a.showCount(); //输出对象个数
	Point b(a); //定义对象b,其构造函数回使count增1
	cout << "Point B: " << b.getX() << ", " << b.getY();
	b.showCount(); //输出对象个数
	return 0;
}
运行结果:
Point A: 4, 5 Object count=1
Point B: 4, 5 Object count=2

类的静态函数成员

  • 类外代码可以使用类名和作用域操作符来调用静态成员函数。
  • 静态成员函数主要用于处理该类的静态数据成员,可以直接调用静态成员函数。
  • 如果访问非静态成员,要通过对象来访问。
#include 
using namespace std;
class Point {
	public:
		Point(int x = 0, int y = 0) : x(x), y(y) { count++; }//构造函数
		Point(Point &p) { //拷贝构造函数
			x = p.x;
			y = p.y;
			count++;
		}
	~Point() { count--; }
	int getX() { return x; }
	int getY() { return y; }
	static void showCount() {
	cout << " Object count = " << count << endl;
	}
	private:
		int x, y;
		static int count; //静态数据成员声明,用于记录点的个数
};
int Point::count = 0;//静态数据成员定义和初始化,使用类名限定
int main() {
	Point a(4, 5); //定义对象a,其构造函数会使count增1
	cout << "Point A: " << a.getX() << ", " << a.getY();
	Point::showCount(); //输出对象个数Point b(a); //定义对象b,其构造函数回使count增1
	cout << "Point B: " << b.getX() << ", " << b.getY();
	Point::showCount(); //输出对象个数
return 0;
}

3.9 类的友元

  • 友元函数不是当前类的成员,而是独立于当前类的外部函数,但它可以访问该类所有成元,包含私有成员、保护成员和公有成员。
  • 友元是C++提供的一种破坏数据封装和数据隐藏的机制。
  • 通过将一个模块声明为另一个模块的友元,一个模块能够引用到另一个模块中本是
    被隐藏的信息。
  • 可以使用友元函数和友元类。
  • 为了确保数据的完整性,及数据封装与隐藏的原则,建议尽量不使用或少使用友元。

友元函数

  • 友元函数是在类声明中由关键字friend修饰说明的非成员函数,在它的函数体中能
    够通过对象名访问 private 和 protected成员
  • 作用:增加灵活性,使程序员可以在封装和快速性方面做合理选择。
  • 访问对象中的成员必须通过对象名。
#include 
#include 
class Point { //Point类声明
public: //外部接口
Point(int x=0, int y=0) : x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(Point &a, Point &b);
private: //私有数据成员
int x, y;
};
float dist( Point& a, Point& b) {
double x = a.x - b.x;
double y = a.y - b.y;return static_cast<float>(sqrt(x * x + y * y));
}
int main() {
Point p1(1, 1), p2(4, 5);
cout <<"The distance is: ";
cout << dist(p1, p2) << endl;
return 0;
}

友元类

  • 若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
  • 声明语法:将友元类名在另一个类中使用friend修饰说明。
class A {
	friend class B;
	public:
		void display() {
		cout << x << endl;
		}
	private:
		int x;
};
class B {
	public:
		void set(int i);
		void display();
	private:
		A a;
};
void B::set(int i) {
	a.x=i;
}
void B::display() {
	a.display();
}

类的友元关系是单向的

如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A
类的成员函数却不能访问B类的私有、保护数据。

3.10常类型

常对象

必须进行初始化,不能被更新。 const 类名 对象名

class A
{
	public:
		A(int i,int j) {x=i; y=j;}
...
	private:
		int x,y;
};
A const a(3,4); //a是常对象,不能被更新

常成员

用const进行修饰的类成员:常数据成员和常函数成员
常成员函数
- 使用const关键字说明的函数。
- 常成员函数不更新对象的数据成员。
- 常成员函数说明格式:
类型说明符 函数名(参数表)const;
这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键
字。
- const关键字可以被用于参与对重载函数的区分

  • 通过常对象只能调用它的常成员函数。

常数据成员

  • 使用const说明的数据成员。
//常成员函数举例
#include
using namespace std;
class R {
	public:
		R(int r1, int r2) : r1(r1), r2(r2) { }
		void print();
		void print() const;
	private:
		int r1, r2;
};
void R::print() {
	cout << r1 << ":" << r2 << endl;
}
void R::print() const {
	cout << r1 << ";" << r2 << endl;
}
int main() {
	R a(5,4);
	a.print(); //调用void print()
	const R b(20,52);
	b.print(); //调用void print() const
	return 0;
}
#include 
using namespace std;
class A {
	public:
		A(int i);
		void print();
	private:
		const int a;
		static const int b; //静态常数据成员
};
const int A::b=10;
A::A(int i) : a(i) { }
void A::print() {
	cout << a << ":" << b <<endl;
}
int main() {
//建立对象a和b,并以100和0作为初值,分别调用构造函数,
//通过构造函数的初始化列表给对象的常数据成员赋初值
	A a1(100), a2(0);
	a1.print();
	a2.print();
	return 0;
}

常引用
被引用的对象不能被更新,const 类型说明符 &引用名

  • 如果用常引用做形参,便不会意外地发生对实参的更改。
#include 
#include 
using namespace std;
class Point { //Point类定义
	public: //外部接口
		Point(int x = 0, int y = 0): x(x), y(y) { }
		int getX() { return x; }
		int getY() { return y; }
		friend float dist(const Point &p1,const Point &p2);
	private: //私有数据成员
		int x, y;
};
float dist(const Point &p1, const Point &p2) {
	double x = p1.x - p2.x;double y = p1.y - p2.y;
	return static_cast<float>(sqrt(x*x+y*y));
}
int main() { //主函数
	const Point myp1(1, 1), myp2(4, 5);
	cout << "The distance is: ";
	cout << dist(myp1, myp2) << endl;
	return 0;
}

3.11 string 类

  • 使用字符串类string表示字符串:string实际上是对字符数组操作的封装
  • string 类常用的构造函数
    string(); //默认构造函数,建立一个长度为0的串
string s1;
string(const char *s); //用指针s所指向的字符串常量初始化string对象
string s2 = “abc”;
string(const string& rhs); //拷贝构造函数
string s3 = s2;

string 类常用操作

  • s + t 将串s和t连接成一个新串
  • s = t 用t更新s
  • s == t 判断s与t是否相等
  • s != t 判断s与t是否不等
  • s < t 判断s是否小于t(按字典顺序比较)
  • s <= t 判断s是否小于或等于t (按字典顺序比较) s > t 判断s是否大于t (按字典顺序比较)
  • s >= t 判断s是否大于或等于t (按字典顺序比较)
  • s[i] 访问串中下标为i的字符
string s1 = "abc", s2 = "def";
string s3 = s1 + s2; //结果是"abcdef"
bool s4 = (s1 < s2); //结果是true
char s5 = s2[1]; //结果是'e'
//string 类应用举例
#include 
#include 
using namespace std;
//根据value的值输出true或false
//title为提示文字
inline void test(const char *title, bool value)
{
	cout << title << " returns "<< (value ? "true" : "false") << endl;
}
int main() {
	string s1 = "DEF";
	cout << "s1 is " << s1 << endl;
	string s2;
	cout << "Please enter s2: ";
	cin >> s2;
	cout << "length of s2: " << s2.length() << endl;
	//比较运算符的测试
	test("s1 <= \"ABC\"", s1 <= "ABC");
	test("\"DEF\" <= s1", "DEF" <= s1);//连接运算符的测试
	s2 += s1;
	cout << "s2 = s2 + s1: " << s2 << endl;
	cout << "length of s2: " << s2.length() << endl;
	return 0;
}

注意cin的>>操作符输入字符串,会以空格作为分隔符,空格后的内容会在下一回输
入时被读取,因此,对于含有空格的string 类,则需要用getline可以输入整行字符串(要包string头文件),例如:

// getline 输入字符串
include <iostream>
#include 
using namespace std;
int main() {
	for (int i = 0; i < 2; i++){
	string city, state;
	getline(cin, city, ',');
	getline(cin, state);
	cout << "City:" << city << “ State:" << state << endl;
	}
return 0;
}
运行结果:
Beijing,China
City: Beijing State: China
San Francisco,the United StatesCity: San Francisco State: the United States

第四章

4.1 继承与派生

概述

  • 继承与派生是同一过程从不同的角度看
    • 保持已有类的特性而构造新类的过程称为继承
    • 在已有类的基础上新增自己的特性而产生新类的过程称为派生。
  • 被继承的已有类称为基类(或父类)
  • 派生出的新类称为派生类(或子类)
  • 直接参与派生出某类的基类称为直接基类
  • 基类的基类甚至更高层的基类称为间接基类

目的

  • 继承的目的:实现设计与代码的重用。
  • 派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对
    原有程序进行改造。

4.2单继承时派生类的定义

语法

class 派生类名:继承方式 基类名
{
	成员声明;
}

class Derived: public Base
{
	public:
		Derived ();
		~Derived ();
};

4.3 多继承时派生类的定义

语法

class 派生类名:继承方式1 基类名1,继承方式2 基类名2...
{
	成员声明;
}

注意: 每一个“继承方式”,只用于限制对紧随其后之基类的继承。

class Derived: public Base1, private Base2
{
	public:
		Derived ();
		~Derived ();
};

4.4 继承方式

不同继承方式的影响主要体现在:

  • 派生类成员对基类成员的访问权限
  • 通过派生类对象对基类成员的访问权限

三种继承方式

  • 公有继承
  • 私有继承
  • 保护继承
    公有继承(public)
  • 继承的访问控制
    • 基类的public和protected成员:访问属性在派生类中保持不变
    • 基类的private成员:不可直接访问
  • 访问权限
    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但
      不能直接访问基类的private成员;
    • 通过派生类的对象:只能访问public成员。
#include 
#include 
using namespace std;
class Point { //基类Point类的定义
	public: //公有函数成员
		void initPoint(float x = 0, float y = 0)
			{ this->x = x; this->y = y;}
		void move(float offX, float offY)
			{ x += offX; y += offY; }
		float getX() const { return x; }
		float getY() const { return y; }private: //私有数据成员
		float x, y;
};
class Rectangle: public Point { //派生类定义部分
	public: //新增公有函数成员
		void initRectangle(float x, float y, float w, float h) {
		initPoint(x, y); //调用基类公有成员函数
		this->w = w;
		this->h = h;
		}
	float getH() const { return h; }
	float getW() const { return w; }
	private: //新增私有数据成员
		float w, h;
};
int main() {
	Rectangle rect; //定义Rectangle类的对象
	//设置矩形的数据
	rect.initRectangle(2, 3, 20, 10);
	rect.move(3,2); //移动矩形位置
	cout << "The data of rect(x,y,w,h): " << endl;//输出矩形的特征参数
	cout << rect.getX() <<", "<< rect.getY() << ", "<< rect.getW() << ", "<< rect.getH() <<endl;
	return 0;
}

私有继承

  • 继承的访问控制
    • 基类的public和protected成员:都以private身份出现在派生类中;
    • 基类的private成员:不可直接访问。
  • 访问权限
    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:不能直接访问从基类继承的任何成员。
#include 
#include 
using namespace std;
class Point { //基类Point类的定义
	public: //公有函数成员
		void initPoint(float x = 0, float y = 0)
			{ this->x = x; this->y = y;}
		void move(float offX, float offY)
			{ x += offX; y += offY; }
		float getX() const { return x; }
		float getY() const { return y; }
	private: //私有数据成员
		float x, y;
};
class Rectangle: private Point { //派生类定义部分
	public: //新增公有函数成员
		void initRectangle(float x, float y, float w, float h) {
			initPoint(x, y); //调用基类公有成员函数
			this->w = w;
			this->h = h;
		}
		void move(float offX, float offY) { Point::move(offX, offY); }
		float getX() const { return Point::getX(); }//这样就可以直接用派生类的对象直接访问。
		float getY() const { return Point::getY(); }
		float getH() const { return h; }
		float getW() const { return w; }
	private: //新增私有数据成员
		float w, h;
};


int main() {
	Rectangle rect; //定义Rectangle类的对象
	rect.initRectangle(2, 3, 20, 10); //设置矩形的数据
	rect.move(3,2); //移动矩形位置
	cout << "The data of rect(x,y,w,h): " << endl;
	//输出矩形的特征参数
	cout << rect.getX() <<", " << rect.getY() << ", "<< rect.getW() << ", "<< rect.getH() << endl;
	return 0;
}

保护继承(protected)

  • 继承的访问控制
    • 基类的public和protected成员:都以protected身份出现在派生类中;
    • 基类的private成员:不可直接访问。
  • 访问权限
    • 派生类中的成员函数:可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;
    • 通过派生类的对象:不能直接访问从基类继承的任何成员。

protected 成员的特点与作用

  • 对建立其所在类对象的模块来说,它与 private 成员的性质相同。
  • 对于其派生类来说,它与 public 成员的性质相同。
  • 既实现了数据隐藏,又方便继承,实现代码重用。
class A {
	protected:
	int x;
};
int main() {
	A a;
	a.x = 5; //错误
}
class A {
	protected:
		int x;
};
class B: public A{
	public:void function();
};
void B:function() {
	x = 5; //正确
}

在派生类 B 的成员函数 functÎon 内部,是完全可以访问基类的保护成员的。
==注意 ==如果 B 是 A 的派生类, B 的成员函数只能通过 B 的对象访问 A 中定义的protected 成员,而不能通过 A 的对象访问 A 的 protected 成员。
c++面向对象程序设计课程笔记(简单易于理解)_第3张图片

4.5 派生类的构造函数

如果派生类有自己新增的成员,且需要通过构造函数初始化,则派生类要自定义构造函数。
若不完全继承基类的构造函数

  • 派生类新增成员:派生类定义构造函数初始化;
  • 继承来的成员:自动调用基类构造函数进行初始化;
  • 派生类的构造函数需要给基类的构造函数传递参数。
    单继承
  • 派生类只有一个直接基类的情况,是单继承。单继承时,派生类的构造函数只需要给一个直接基类构造函数传递参数。
//单继承
#include
using namespace std;
class B {
	public:
		B();
		B(int i);
		~B();
		void print() const;
	private:
		int b;
};
B::B() {
	b=0;
	cout << "B's default constructor called." << endl;
}
B::B(int i) {
	b=i;
	cout << "B's constructor called." << endl;
}
B::~B() {
	cout << "B's destructor called." << endl;
}
void B::print() const {
	cout << b << endl;
}
class C: public B {
	public:
		C();
		C(int i, int j);
		~C();
	void print() const;
	private:
		int c;
};
C::C() {
	c = 0;
	cout << "C's default constructor called." << endl;
}
C::C(int i,int j): B(i), c(j){
	cout << "C's constructor called." << endl;
}
C::~C() {
	cout << "C's destructor called." << endl;
}
void C::print() const {
	B::print();
	cout << c << endl;
}
int main() {
	C obj(5, 6);
	obj.print();
	return 0;
}

多继承
多继承时,有多个直接基类,如果不继承基类的构造函数,派生类构造函数需要给所有基类构造函数传递参数。我们来看一下语法规定

  • 多继承时构造函数的定义语法
    派生类名::派生类名(参数表) :
    基类名1(基类1初始化参数表),
    基类名2(基类2初始化参数表),

    基类名n(基类n初始化参数表),
    本类成员初始化列表{
    //其他初始化;
    };

派生类与基类的构造函数

  • 当基类有默认构造函数时:
    1. 派生类构造函数可以不向基类构造函数传递参数。
    2. 构造派生类的对象时,基类的默认构造函数将被调用。
  • 如需执行基类中带参数的构造函数
    派生类构造函数应为基类构造函数提供参数。

多继承且有对象成员时派生的构造函数定义语法

派生类名::派生类名(形参表):
基类名1(参数), 基类名2(参数), …, 基类名n(参数),
本类成员(含对象成员)初始化列表
{
//其他初始化
};

构造函数的执行顺序

  1. 调用基类构造函数。
    • 顺序按照它们被继承时声明的顺序(从左向右)
  2. 对初始化列表中的成员进行初始化。
    • 顺序按照它们在类中定义的顺序。
    • 对象成员初始化时自动调用其所属类的构造函数。由初始化列表提供参数。
  3. 执行派生类的构造函数体中的内容。

注意:构造函数初始化列表中基类名、对象名之间的次序无关紧要,它们各自出现的顺序可以是任意的,无论它们的顺序怎样安排,基类构造函数的调用和各个成员对象的初始化顺序都是确定的。

派生类构造函数举例(多继承、含有内嵌对象)。
这是一个具有一般性特征的例子,有 3 个基类 Basel , Base2 和 Base3 。其中 Base3 只有一个默认的构造函数,其余两个基类的成员只是一个带有参数的构造函数。类 Derived由这 3 个基类经过公有派生而来。派生类新增加了 3 个私有对象成员,分别是 Basel,Base2 和 Base3 类的对象。程序如下:

# include < iostream>
using namespace std;
class Basel {
	public:
	//基类 Basel ,构造函数有参数
		Base1 (int i) {cout<< "Constructing Base1 "<< i<< endl; }
};
class Base2 (
	public:
//基类 Base2 ,构造函数有参数
		Base2( int j) {cout<< "Constructing Base2 "<< j<< endl; }
};
class Base3 {
	public:
		Base3 () {cout<< "Constructing Base3 铃 "<<endl; }
		//基类 Base3 ,构造函数无参数
};

class Derived: public Base2, public Basel, public Base3 (
// 派生新类 Derived,注意基类名的顺序
	public: //派生类的公有成员
		Derived(int a ,工nt b, int c, int d) : Basel(a),member2(d),member1(c),Base2(b)
{}
//注意基类名的个数与顺序,注意成员对象名的个数与顺序
	private:
		Base1 member1;
		Base2 member2;
		Base3 member3;
int main () {
	Derived obj (1 , 2, 3, 4);
	return 0;


派生类的私有成员对象程序的运行结果为:
Constructing Base2 2
Constructing Basel 1
Constructing Base3 *
Constructing Basel 3
Constructing Base2 4
Constructing Base3'*

Derived 类构造函数的执行情况,它应该是先调用基类的构造函数,然后调用内嵌对象的构造函数。基类构造函数的调用顺序是按照派生类定义时的顺序,因此应该是先 Base2. 再 Base1. 最后 Base3; 而内嵌对象的构造函数调用顺序应该是按照成员在类中声明的顺序,应该是先 Base1 ,再 Base2 ,最后 Base3 .

派生类构造函数定义中,并没有显式列出基类 Base3 和 Base3 类的对象 member3 ,这时系统就会自动调用该类的默认构造函数。

析构函数与构造函数的执行顺序相反,由于时采用的了栈的方式,先进后出。

4.6 同名成员

为了在派生类中使用和基类的同名成员,必须在该成员名前加上基类名和作用域标识符" : : ",
基类名 ::成员名

#include 
using namespace std;
class Base1 {
	public:
		int var;
		void fun() { cout << "Member of Base1" << endl; }
};
class Base2 {
	public:
		int var;
		void fun() { cout << "Member of Base2" << endl; }
};
class Derived: public Base1, public Base2 {
	public:
		int var;
		void fun() { cout << "Member of Derived" << endl; }
};
int main() {
	Derived d;
	Derived *p = &d;
	//访问Derived类成员
	d.var = 1;
	d.fun();
	//访问Base1基类成员
	d.Base1::var = 2;
	d.Base1::fun();
	//访问Base2基类成员
	p->Base2::var = 3;
	p->Base2::fun();
	return 0;
}

多继承时的二义性和冗余问题

#include 
using namespace std;
class Base0 { //定义基类Base0
	public:
		int var0;
		void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: public Base0 { //定义派生类Base1public: //新增外部接口
	int var1;
};
class Base2: public Base0 { //定义派生类Base2
	public: //新增外部接口
	int var2;
};
class Derived: public Base1, public Base2 {
	public:
		int var;
		void fun()
		{ cout << "Member of Derived" << endl; }
};
int main() { //程序主函数
	Derived d;
	d.Base1::var0 = 2;
	d.Base1::fun0();
	d.Base2::var0 = 3;
	d.Base2::fun0();
	return 0;
}

c++面向对象程序设计课程笔记(简单易于理解)_第4张图片

4.7 虚基类

  • 需要解决的问题
    • 当派生类从多个基类派生,而这些基类又共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性
  • 虚基类声明
    • 以virtual说明基类继承方式
    • 例:class B1:virtual public B
  • 作用
    • 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题
    • 为最远的派生类提供唯一的基类成员,而不重复产生多次复制

注意:
在第一级继承时就要将共同基类设计为虚基类。

c++面向对象程序设计课程笔记(简单易于理解)_第5张图片

#include 
using namespace std;
class Base0 {
	public:
		int var0;
		void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
	public:
		int var1;
};
class Base2: virtual public Base0 {
	public:
		int var2;
};
class Derived: public Base1, public Base2 {
//定义派生类Derived
	public:
		int var;
		void fun() {
		cout << "Member of Derived" << endl;}
};
int main() {
	Derived d;
	d.var0 = 2; //直接访问虚基类的数据成员
	d.fun0(); //直接访问虚基类的函数成员
	return 0;
}

虚基类及其派生类构造函数

  • 建立对象时所指定的类称为最远派生类。
  • 虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
  • 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中为虚基类的构造函数列出参数。如果未列出,则表示调用该虚基类的默认构造函数。
  • 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。因此,虚基类的构造函数只执行了一次。
//虚基类时的构造函数举例
#include 
using namespace std;
class Base0 {
	public:
		Base0(int var) : var0(var) { }
		int var0;
		void fun0() { cout << "Member of Base0" << endl; }
};
class Base1: virtual public Base0 {
	public:
		Base1(int var) : Base0(var) { }
		int var1;
};
class Base2: virtual public Base0 {
	public:
		Base2(int var) : Base0(var) { }
		int var2;
};
class Derived: public Base1, public Base2 {
	public:
		Derived(int var) : Base0(var), Base1(var), Base2(var)
		{ }
		int var;
		void fun()
		{ cout << "Member of Derived" << endl; }
};
int main() { //程序主函数
	Derived d(1);
	d.var0 = 2; //直接访问虚基类的数据成员
	d.fun0(); //直接访问虚基类的函数成员
	return 0;
}

虚基类构造函数的调用顺序

  1. 若同一层中同时包含虚基类和非虚基类,应该先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。
  2. 对于多个虚基类,构造函数的执行顺序是先左后右,自上而下。
  3. 对于非虚基类也是相同的

第五章

5.1 多态性

运算符重载

重载为类成员的运算符函数定义形式
函数类型 operator 运算符(形参)
{
......
}
参数个数=原操作数个数-1 (后置++--除外)

双目运算符重载规则

  • 如果要重载 B 为类成员函数,使之能够实现表达式 oprd1 B oprd2,其中 oprd1为A 类对象,则 B 应被重载为 A 类的成员函数,形参类型应该是 oprd2 所属的类型。
  • 经重载后,表达式 oprd1 B oprd2 相当于 oprd1.operator B(oprd2)
#include 
using namespace std;
class Complex {
	public:
		Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) { }
		//运算符+重载成员函数
		Complex operator + (const Complex &c2) const;
		//运算符-重载成员函数
		Complex operator - (const Complex &c2) const;
		void display() const; //输出复数
	private:
		double real; //复数实部
		double imag; //复数虚部
};
Complex Complex::operator+(const Complex &c2) const{
//创建一个临时无名对象作为返回值
	return Complex(real+c2.real, imag+c2.imag);
}
	Complex Complex::operator-(const Complex &c2) const{
	//创建一个临时无名对象作为返回值
	return Complex(real-c2.real, imag-c2.imag);
}
void Complex::display() const {
	cout<<"("<<real<<", "<<imag<<")"<<endl;
}
int main() {
	Complex c1(5, 4), c2(2, 10), c3;
	cout << "c1 = "; c1.display();
	cout << "c2 = "; c2.display();
	c3 = c1 - c2; //使用重载运算符完成复数减法
	cout << "c3 = c1 - c2 = "; c3.display();
	c3 = c1 + c2; //使用重载运算符完成复数加法
	cout << "c3 = c1 + c2 = "; c3.display();
	return 0;
}

前置单目运算符重载规则

  • 如果要重载 U 为类成员函数,使之能够实现表达式 U oprd,其中 oprd 为A类对象,则 U 应被重载为 A 类的成员函数,无形参。
  • 经重载后,
    表达式 U oprd 相当于 oprd.operator U()

后置单目运算符 ++和–重载规则

  • 如果要重载 ++或–为类成员函数,使之能够实现表达式 oprd++ 或 oprd-- ,其中 oprd 为A类对象,则 ++或-- 应被重载为 A 类的成员函数,且具有一个 int 类型形参。
  • 经重载后,表达式 oprd++ 相当于 oprd.operator ++(0)
#include 
using namespace std;class Clock {//时钟类定义
	public:
		Clock(int hour = 0, int minute = 0, int second = 0);
		void showTime() const;
//前置单目运算符重载
		Clock& operator ++ ();
//后置单目运算符重载
		Clock operator ++ (int);
	private:
		int hour, minute, second;
};
Clock::Clock(int hour, int minute, int second) {
	if (0 <= hour && hour < 24 && 0 <= minute && minute < 60
&& 0 <= second && second < 60) {
	this->hour = hour;
	this->minute = minute;
	this->second = second;
} else
	cout << "Time error!" << endl;
}
void Clock::showTime() const { //显示时间
	cout << hour << ":" << minute << ":" << second << endl;
}
Clock & Clock::operator ++ () {
	second++;
	if (second >= 60) {
		second -= 60; minute++;
	}
	if (minute >= 60) {
		minute -= 60; hour = (hour + 1) % 24;
	}return *this;
}
Clock Clock::operator ++ (int) {
	//注意形参表中的整型参数
	Clock old = *this;
	++(*this); //调用前置“++”运算符
	return old;
}
int main() {
	Clock myClock(23, 59, 59);
	cout << "First time output: ";
	myClock.showTime();
	cout << "Show myClock++: ";
	(myClock++).showTime();
	cout << "Show ++myClock: ";
	(++myClock).showTime();
	return 0;
}

后面的部分重要,浓缩不了知识点,直接看书会更好一些。

第八章

8. 容器

vectorstackqueuemapset 这些在C++中统称为容器,这些大小可以用.size()获取。

8.1 vector (向量/动态数组)

vector 定义

  • vector v // 没有分配大小,v.size()为0.
  • vector v(10) // 定义一个长度为10的int数组,默认10个元素值都为0,
  • vector v3(100, 9);// 把100⻓度的数组中所有的值都初始化为9
  • vector v1; v1.resize(8); //先定义⼀个vector变量v1,然后将⻓度resize为8,默认这8个元素都是0

vector 遍历:`:

for (auto it = c.begin(); it != c.end(); it++) { 
cout << *it << " "; // 使⽤迭代器的⽅式访问vector
}
for (int i = 0; i < c.size(); i++) {
cout << c[i] << " ";
}

vector中常用函数

  1. push_back(x) 就是在vector后面添加一个元素x
  2. pop_back() 删除vector的尾元素
  3. size() 计算vector 的动态数组长度
  4. clear()

8.2 map(映射)

在头文件====中:

8.2.1如何理解map中的映射:

  • 相当于array[0] = 5.20,是将int 型映射到double型。
  • map 可以将任何基本类型(包括STL容器)映射到任何基本类型(包括STL容器)。

8.2.2如何定义map:

  • 字符串到整型的映射:map mp; 字符串必须只能用string表示。
  • 字符型到整型的映射:map mp;
  • set容器到字符串类型的映射:map, string> mp;

8.2.3map的访问:

  1. 通过下标访问:
map<char,int> mp;
mp['c'] = 20;
mp['c'] = 30printf("%d\n",mp['c']); // 结果为30
  1. 通过迭代器访问
for(auto it = j.begin(); it!= m.end();it++){
	cout<< it->fist<<" "<< it->second << endl;
}
// 访问map的第⼀个元素,输出它的键和值
cout << m.begin()->first << " " << m.begin()->second << endl;
// 访问map的最后⼀个元素,输出它的键和值
cout << m.rbegin()->first << " " << m.rbegin()->second << endl;
// 输出map的元素个数
cout << m.size() << endl;

8.2.4map常用函数:

  1. find()
    find(key)返回键为key 的映射的迭代器,时间复杂度为O(log~N),N为map中映射的个数。
  2. erase()
    删除单个元素:mp.erase(it) // it为迭代器;mp.erase(‘b’)
    删除一个区间内的所有元素:mp.erase(first,last) //
  3. size() 获得map映射的对数
  4. clear() 清空map
map<char,int> mp;
mp['a'] = 1;
map <char,int> mp;
int main(){
	mp['a'] = 1;
	mp['b'] = 2;
	auto it = mp.find('a'); //auto 可以让编译器根据初始值类型直接推断变量的类型
	printf("%c %d\n",it->first,it->second);
	mp.erase(it,mp.end());//删除it之后的所有映射。
	mp.erase(it) //就把mp['a']删除了
	mp.clear();
}

8.2.5常见用途:

  1. 需要建立字符(或字符串)与整数之间映射的题目,使用map可以减少代码量。
  2. 判断大整数或者其他类型数据是否存在的题目,可以把map当bool数组用
  3. 字符串和字符串的映射也可能会遇到。
    叮叮~具体应用可以参考本人写的文章::PAT1018

8.3set(集合)

因为是集合所以有着互异性,set里面的各元素是各不相同的,而且set会按照元素进行从小到大排序

8.3.1set的定义

set s; 定义一个空集合s,其中typename 中可以是double,结构体,类,或者是其他容器。
set t[n] ; 则每个t[0]~t[n-1]每一个都是一个set容器。

8.3.2set内元素的访问

只能通过迭代器访问,例如:set :: interator it;

for(set \<int> ::interator it = s.begin();it!= s.end(
;it++){
		printf("%d",*it)
}

或者,将set ::interator 用auto 表示

8.3.3set常用函数:

  1. insert()
for (int i = 0; i < 6; i++) {
s.insert(i); // 向集合s⾥⾯插⼊i
}
  1. find()
*(s.find(2))  // 如果2存在,则printf 结果为2。
s.find(2)!=s.end() // 判断该元素是否存在
  1. erase()
    s.erase(s.find(1)) // 删除单个元素 ,这个方法空间复杂度为0(1)
    s.erase(100) // 删除的单个元素,这个是个为O(logn)
    s.erase(s.find(300),s.end()) // 删除元素300到set末尾之间的元素
  2. size()
    所有容器通用的获得容器大小的函数。
    s.size()
  3. clear()
    s.clear() //清空set 容器,则s.size() = 0;

8.4 stack

头文件在 中,加上命名空间。

8.4.1 stack 的定义

stack s; // 类型和上面的容器相同,可以是基本数据类型或者容器

8.4.2 stack 容器内元素的访问

这里的栈和数据结构中的栈一样,只能通过top()来访问栈顶元素

for (int i = 0; i < 6; i++) {
s.push(i); // 将元素i压⼊栈s中
}
while(s.size()!=0){
	cout << s.top() << endl; // 访问s的栈顶元素
	s.pop(); //弹出栈顶元素
} // 相当于遍历所有的元素,并且遍历完后s 中的元素会被清空。

8.4.3stack常用函数:

  1. push()
  2. pop()
  3. top()
  4. size()
  5. empty() // 检测stack内是否为空,返回ture为空,返回false为非空

8.5 queue队列

头文件在 中,加上命名空间。

8.5.1 queue 的定义

queue q; // 类型和上面的容器相同,可以是基本数据类型或者容器

8.5.2 queue 容器内元素的访问

这里的队列和数据结构中的栈一样,只能通过front()来访问队首元素,或者用back()来访问队尾元素。

queue<int> q; // 定义⼀个空队列q
for (int i = 0; i < 6; i++) {
q.push(i); // 将i的值依次压⼊队列q中
}
printf("%d %d\n",q.front(),q.back()); // 访问队列的队⾸元素和队尾元素  ,结果为1 5
cout << q.size() << endl; // 输出队列的元素个数
q.pop() //弹出首个元素

8.5.3queue常用函数:

  1. push()
  2. pop()
  3. front()
  4. back()
  5. size()
  6. empty() // 在使用front和pop()前,最好判断下队列是否为空。

勉励
不要留给自己下一次再来的机会!(*╹▽╹*)
申明:
本文参考资料:

  1. 郑莉主编《c++语言程序设计(第四版)》
  2. 陈维兴主编《c++语言面向对象程序设计教程》

建议:读者如果有时间的话,最好自己码码。

如果觉得我的文章对你有所帮助与启发,点赞给我个鼓励吧(づ ̄3 ̄)づ╭❤~
关注我和我一起共勉加油吧!
如果文章有错误,还望不吝指教!

你可能感兴趣的:(笔记)