知识导图 知识导图
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
  • MD

  • Vue

  • C语法

  • C++语法

    • C++基础概念(上)
    • C++基础概念(下)
    • C++语法速查
    • C++面向对象(上)
      • 一、类 & 对象
        • 1.1类 & 对象
        • 1.2类定义
        • 1.3对象定义
        • 1.4访问数据成员
        • 1.5类的成员函数
        • 1.6访问修饰符
        • 类构造函数 & 析构函数
        • 类对象的拷贝
        • 友元函数
        • 什么是函数
        • 内联函数
        • 指向对象指针: this
        • 指向类指针
        • 静态成员static
      • 二、继承
        • 继承
        • 多继承
    • C++面向对象(下)
    • C++高级教程(上)
    • C++高级教程(中)
    • C++高级教程(下)
  • 汇编语言

  • 软件编程及算法
  • C++语法
2023-06-30
目录

C++面向对象(上)

# 一、类 & 对象

将过程和逻辑抽象出人更容易理解的抽象模块,模拟物种(类)和个体(对象)

# 1.1类 & 对象

C 语言面向过程编程

C++ 面向对象编程:在 C 语言的基础上增加了面向和对象设计,从功能代码转向人类更容易理解的一个个事物,

类: C++ 的核心特性,用于指定对象的形式,包含了【数据表示法】和【处理数据的方法】,被称为类的成员。

# 1.2类定义

类定义是以关键字 class 开头,后跟类的名称。

类只是告诉你数据和方法定义,以及如何执行,本身不具备数据存储能力。

class Person
{
   public:
      double length;  
      int age; 
      string name; 
};

⚠️ 在没有修饰成员类型时,struct 的成员默认访问方式是 public , class 是的成员默认访问方式是 private

所以公开访问的成员,一般class 要假设public 修饰,而结构体公开的不需要,不设置就是默认公开

# 1.3对象定义

Person 人类是类,那么小明就是一个人(对象)

Person xiaoming;          // 对象xiaoming

# 1.4访问数据成员

完整实例以及数据访问:

#include <iostream>

using namespace std;

class Person
{
   public:
      double height;  
      int age; 
      string name; 
};

int main( )
{
   Person per1;        // 人对象1
 
   // 对象特征设置
   per1.height = 155.0; 
   per1.age = 16.0; 
   per1.name = "小明";

   cout << "身高:" << per1.height <<endl;
   return 0;
}

执行:

身高: 155.0

# 1.5类的成员函数

前面我们通过对象per1 获取 类成员 ”身高“,还有其他方法,就是通过 函数来获取,同时这个函数也是类的一个成员,我们称他为 成员函数

#include <iostream>

using namespace std;

class Person
{
   public:
      double height;  
      int age; 
      string name; 
  		// 成员函数1(⚠️ 在类内同时也是 内联函数)(获取身高)
  		int getAge(void)
      {
         return age;
      }
      // 成员函数2(在类外定义)
      void setAge(int age_new);
  
};

// 在类的外部使用【围解析运算符 :: 】定义成员函数,结构如下:
void Person::setAge(int age_new)
{
   age = age_new;
}

// 上面我们实现了一个get方法和一个set方法,下面拿来使用
int main( )
{
   Person per1;        // 人对象1
 
   // 对象特征设置
   per1.setAge(16.0); 

   cout << "年龄:" << per1.getAge() <<endl;
   return 0;
}

# 1.6访问修饰符

一个类可以有多个 public、protected 或 private 标记区域

# 公有(public)成员

✅ 这些成员都能被对象直接访问,前面例子都是,已经介绍过。

# 私有(private)成员

❌ 显然是不能被对象直接访问的成员,都是在定义时使用

❌ 同时子类也不能在定义中使用私有成员,完全是当前类型唯一使用

# 保护(protected)成员

❌ 不能被对象直接访问的成员,都是在定义时使用

✅ 子类(派生类)可以在定义中使用私有成员

示例:

#include <iostream>

using namespace std;

class Person
{
   public:
  		// 公开成员
      double height;  
  		
  		// 公开成员函数
  		int getAge(void)
      void setAge(int age_new);
  
   private:
      int age;  // 私有成员
  
   protected:
      string name;  // 保护成员
};

// 公开成员函数操作私有成员
int Person::getAge(void)
{
   return age;
}  

void Person::setAge(int age_new)
{
   age = age_new;
}


// 定义子类(派生类):学生👨‍🎓
class Student:Person 
{
   public:
  		// 公开成员函数,操作父类的保护成员
  		string getName(void)
      void setName(string name_new);
}

// 子类公开成员函数操作 保护成员
string Student::getName(void)
{
   return name;
}  

void Student::setName(string name_new)
{
   name = name_new;
}  


// 上面我们实现了一个get方法和一个set方法,下面拿来使用
int main( )
{
   Person per1;        // 声明对象1
   // ⚠️ 我们不能使用 .age 设置或者获取年龄,只能通过 公开的成员函数来操作
   per1.setAge(16.0); 
   cout << "年龄:" << per1.getAge() <<endl;
  
   Student stud1;
   stud1.setName("小明");
   cout << "学生姓名:" << per1.getName() <<endl;
  
   return 0;
}

# 类构造函数 & 析构函数

对象创建调用函数(OC中叫alloc - init 、alloc - initWith xxx),和对象销毁时调用的函数(OC中叫 dealloc)

1.构造:

简单理解就是 对象创建时调用的函数(成员函数)叫 类构造函数(对象分配内存后立即调用的函数)

构造函数通常可以是多种写法,由程序员选择其中一种创建对象

函数方法名称同类名

分配内存

2.析构:

对象销毁时调用的函数,由系统自动调用,

析构函数通常只有一种固定写法

函数方法名称同类名,但是前面需要加 "~" 修饰

清理销毁对象的内存

#include <iostream>
 
using namespace std;
 
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 构造函数
  		Line(double len);  // 这是带参数的构造函数
 
  		~Line();  // 这是析构函数声明
   private:
      double length;
};
 
// 构造函数(成员函数)定义
Line::Line(void)
{
    cout << "对象被创建" << endl;
}

// 带参数的构造函数定义,同时设置成员数据
Line::Line( double len)
{
    cout << "对象被创建, length = " << len << endl;
    length = len;
}

Line::~Line(void)
{
    cout << "对象被销毁(对象释放时,系统自动调用此成员函数)" << endl;
}
 
// set方法
void Line::setLength( double len )
{
    length = len;
}
 
// get 方法
double Line::getLength( void )
{
    return length;
}

// 程序的主函数
int main( )
{
   Line line;
   // 或者
   Line line(88.0);

   cout << "Length of line : " << line.getLength() <<endl;
 
   return 0;
}

结果

对象被创建, length = 88
Length of line : 88
对象被销毁(对象释放时,系统自动调用此成员函数)

另外,当一个对象有多个成员时,构造函数,可以使用初始化列表来初始化字段:

class Point
{
   public:
      double X;
  		double Y;
      Point(double x, double y);  // 这是构造函数
};

// 方式1: 构造函数 
Point::Point(double x, double y)
{
  cout << "构造函数" << endl;
  X = x;
  Y = y;
}

// 方式2: 初始化列表方式的构造函数-(效果等同方式1)
Point::Point(double x, double y): X(x), Y(y)
{
  cout << "构造函数-初始化列表方式" << endl;
}

# 类对象的拷贝

类对象的复制比简单的数据类型的复制要复杂一些,随着成员的复杂,克隆人的复制流程会更复杂。

1.简单的类对象复制:

// 程序的主函数
int main( )
{
  
   
   int a = 88;
	 int b = a; // 普通的值拷贝,也叫赋值
  
   Line line(88.0);
   Line line2 = line; // 本质是调用【拷贝构造函数】,而非赋值
	
   // 如果用户没有实现 拷贝构造函数,那么系统会自动生成对应的拷贝构造函数
 
   return 0;
}

2.如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数,

下面实现一个带有指针成员的类构造函数和对象拷贝,完整示例如下:

#include <iostream>

using namespace std;

class Line
{
   public:
      int getLength( void );
      Line( int len );           // 构造函数
      Line( const Line &obj); 	 // 拷贝构造函数
      ~Line();                   // 析构函数

   private:
      int *ptr;
};

// 构造函数
Line::Line(int len)
{
    cout << "为指针分配内存" << endl;
    ptr = new int;
    *ptr = len;
}

// 拷贝构造函数(传入是同类型对象地址)
Line::Line(const Line &obj)
{
    cout << "为指针拷贝内存地址." << endl;
    ptr = new int;
   *ptr = *obj.ptr; // copy
}

// 析构函数
Line::~Line(void)
{
    cout << "内存释放" << endl;
    delete ptr;
}

// get成员函数
int Line::getLength( void )
{
    return *ptr;
}

// 程序的主函数
int main( )
{
   Line line(88);
	 Line line2 = line;

   return 0;
}

# 友元函数

声明:函数声明在某个类中,并使用关键字 friend 修饰

定义:函数的实现不属于某个类,是文件级别的

先看示例:

#include <iostream>
 
using namespace std;
 
class Box
{
   
  public:
  	 double height;
     Box(double len, double wid, double hei);  // 构造函数
     
  	 friend void printVolume(Box box); // 友元函数:获取体积
  
  private:
      double width;
  protected:
      double length;
  
};

// 构造函数
Box::Box(double len, double wid, double hei): length(len), width(wid), height(hei)
{
  cout << "构造函数-初始化列表方式" << endl;
}

//  ✅ 友元函数: ⚠️ 这个不是Box成员函数,函数前面不需要 Box 限制
void printVolume(Box box)
{
   /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
   cout << "box 体积 : " << box.length * box.width * box.height  <<endl;
}
 
// 程序的主函数
int main( )
{
   Box box(3, 4, 5);
   
   // 使用友元函数输出体积
   printVolume( box );
 
   return 0;
}

运行结果:

构造函数-初始化列表方式
box 体积 : 60

# 什么是函数

前面介绍了多种形式的函数,构造函数,析构函数,成员函数,友元函数等等

为了更好地理解后面要介绍的其他函数,这里开始介绍函数的基本概念

先看一段代码:

#include <iostream>
 
using namespace std;
 
 
// 程序的主函数
int main( )
{
  int a = 3;
  int b = 5;
  // 求最大值并打印输出
  int max1 = a > b ? a : b;
  cout << a << "和" << b << "中最大值 : " << max1 <<endl;


  int a2 = 30;
  int b2 = 6;
  // 求最大值并打印输出
  int max2 = a2 > b2 ? a2 : b2;
  cout << a2 << "和" << b2 << "中最大值 : " << max2 <<endl;


  int a3 = 8;
  int b3 = 50;
  // 求最大值并打印输出
  int max3 = a3 > b3 ? a3 : b3;
  cout << a3 << "和" << b3 << "中最大值 : " << max3 <<endl;


  // 假如后面还要执行成百上千次呢?
  
   return 0;
}

打印结果:

3和5中最大值 : 5
30和6中最大值 : 30
8和50中最大值 : 50

有么有发现什么问题❓

相同的功能,重复的代码逻辑,假如需要执行一万次最大值,会发生什么:

1、你写到什么时候能写完

2、代码文件会很大很大

显然这都是不可取的,假如我们这样写:

#include <iostream>
 
using namespace std;
 
//  ✅ 打印输出最大值
void p_max(int a, int b) 
{
	int max1 = a > b ? a : b;
	cout << a << "和" << b << "中最大值 : " << max1 <<endl;
}
 
// 程序的主函数
int main( )
{
  	// 求最大值并打印输出
   	p_max(3, 5);
   	p_max(30, 6);
   	p_max(8, 50);

   return 0;
}

这样就很清晰了,将重复的代码逻辑抽离,并重复使用一份代码,抽离的代码调用就是函数调用。

一万次只需要一次递归或者其它循环就能完成,代码量很少

(1)函数的由来

一个程序经常会通过多次执行相同或者相近功能的程序段来完成,在早期的程序设计中,这些重复的功能段必须通过重复书写代码来实现。这样,不仅会引起重复的劳动、增加程序的长度、造成代码的不一致,而更重要的是,大量重复的程序代码不利于程序的立即与理解。

于是人们将功能重复的程序段抽象出来形成一个独立的功能模块,并为它命名,程序中凡是用到此功能模块的地方就用他的名字代替,这样避免了重复设计的缺点。这种抽象出来的功能模块成为函数或者子程序。

(2)函数的执行过程

当调用一个函数时,整个调用过程分为三步进行,

第一步:函数调用 1、将函数调用语句下一条语句的地址保存到在栈中,以便函数调用完成后返回。(将函数放到栈空间中称为压栈)。

2、对实参表从后向前,一次计算出实参的值,并且将值压栈。

3、跳转到函数体处。

第二步:函数体执行

4、如果函数体中定义了变量,将变量压栈

5、将每一个形参以栈中对应的实参值取代,执行函数体的功能体。

6、将函数体中的变量、保存到栈中的实参值,依次从栈中取出,释放栈空间(出栈)。

第三步:返回

7、返回过程执行的是函数体中的return语句。其过程是从栈中取出刚开始调用函数时压入的地址,跳转到函数的下一条语句。当return语句不带有表达式时,按照保存的地址返回,当return语句带有表达式时,将计算出的return表达式的值保存起来,然后再返回。

⚠️ 这些压栈,出栈,跳转 等等一系列操作都是会额外增加耗时的,但是这些耗时相对于多行代码的执行来说可以忽略。相对于重复的代码劳动可以忽略,相对于大量重复代码导致文件成倍增加可以忽略...

# 内联函数

内联:函数内部直接关联代码块(不一定理解对,但是便于记忆)

外联:函数外部关联某些代码块

前面介绍 “最大值并打印输出” 重复代码的写法 就是函数内部直接关联,其实就是内联的本质。

当我们执行一段代码需要通过文件引用,或者调用其他函数时,就是外联。

**比如:**以前端代码为例

当css 样式代码直接写在 html中,可以理解为 内联

当css 样式代码通过css文件引入 html中,可以理解为 外联

下面修改,也就是函数 前添加 inline :

#include <iostream>
 
using namespace std;
 
// 打印输出最大值

// ⚠️  ✅ 函数前添加 inline 就可以表示内联函数
inline void p_max(int a, int b) 
{
	cout << a << "和" << b << "中最大值 : " << a > b ? a : b <<endl;
}
 
// 程序的主函数
int main( )
{
  	// 求最大值并打印输出
   	p_max(3, 5);
   	p_max(30, 6);
   	p_max(8, 50);

   return 0;
}

那么这段代码再执行编译后,真实的代码逻辑类似这样的:

(实际上会进行进一步优化,这里写法为了方便比较)

#include <iostream>
 
using namespace std;

// 程序的主函数
int main( )
{
  	// 求最大值并打印输出
  int a = 3;
  int b = 5;
  cout << a << "和" << b << "中最大值 : " << (a > b ? a : b) <<endl;


  int a2 = 30;
  int b2 = 6;
  cout << a2 << "和" << b2 << "中最大值 : " << (a2 > b2 ? a2 : b2) <<endl;


  int a3 = 8;
  int b3 = 50;
  cout << a3 << "和" << b3 << "中最大值 : " << (a3 > b3 ? a3 : b3) <<endl;
   
  return 0;
}

很显然,这又回到了先前重复代码写法的方式

没错,本质就是这样,内联函数在编译时直接将代码块插入到需要的地方,哪里需要插入到哪里。

【⚠️ 类的成员函数也是内联函数,不需要显示使用 inline,我的理解是多个相同的类,创建不同的对象时,本质上都有一份对应的代码,显然这些代码都是重复的,与内联本质不谋而合(这样方便记忆)】

其他主要点:

1、普通函数需要 保存寄存器,压栈,出栈,链接,调用等等动作,如果一个函数的代码及其简单,本身已经小于一个函数调用产生的消耗,那么直接内嵌代码执行速度更快,即通过重复的代码牺牲空间来提高速度。(空间换时间)

2、当一个函数执行代码消耗远大于函数调用的开销,那么这个函数就不能成为内联函数,即使你 使用 inline修饰函数,编译时依然当成一个普通函数使用

3、当一个函数满足内联要求,但是通过递归,或者for循环等进行大量的重复操作,那么最终也不能成为内联函数

4、对内联函数进行任何修改,都需要重新编译函数的所有客户端,否则为编译的地方依然使用旧的逻辑

5、你的内联函数通常就是一两行简单的代码

6、同普通函数一样,除了声明,还要实现定义

# 指向对象指针: this

就是对象的成员函数中隐藏一个参数 this,指向当前对象的地址。(OC中叫 self,一个意思)

友元函数不是成员函数,没有this隐藏参数。

示例:this->Area()

#include <iostream>
 
using namespace std;

class Box
{
   public:
      // 构造函数定义:长,宽,高
      Box(double l=2.0, double w=2.0, double h=2.0)
      {
         length = l;
         width = w;
         height = h;
      }

      double Area()
      {
         return length * width;
      }

      double Volume()
      {
         // ✅ 通过 this指针访问面积函数
         return this->Area() * height;
      }

   private:
      double length;     //长
      double width;    //宽
      double height;     //高
};

int main(void)
{
   Box Box1(13, 7, 44); 
   cout << "box 体积 : " << Box1.Volume()  <<endl;

   return 0;
}

结果

box 体积 : 4004

# 指向类指针

通过【*】声明一个指向类的指针,同时这个指针存储某个对象的内存地址。

通过这个指针访问类成员函数,本质上就是对象调用其成员函数一个道理。

修改上面代码示例如下:

#include <iostream>
 
using namespace std;

class Box
{
   public:
      // 构造函数定义:长,宽,高
      Box(double l=2.0, double w=2.0, double h=2.0)
      {
         length = l;
         width = w;
         height = h;
      }

      double Area()
      {
         return length * width;
      }

      double Volume()
      {
         //通过 this指针访问面积函数
         return this->Area() * height;
      }

   private:
      double length;     //长
      double width;    	 //宽
      double height;     //高
};

int main(void)
{
   Box Box1(13, 7, 44); 
   cout << "box 体积 : " << Box1.Volume()  <<endl;

   Box *ptrBox; //  ✅ 声明一个指向类的指针
   // 指针保存类对象的地址
   ptrBox = &Box1;
   // 使用指针访问成员,通过->访问
   cout << "指针访问 box 体积: " << ptrBox->Volume() << endl;

   return 0;
}

# 静态成员static

几个要点:

1、静态意思就是非变化的值,相关值都必须是静态的

2、静态成员只有一份:比如一个类的静态成员,在多个不同的对象中都是共用一份的,不会随着对象的销毁而销毁(实际上静态成员内存和对象的内存不在一个区的,所以生命周期不一样)

3、类外部通过运算符 :: 来调用

示例:

#include <iostream>
 
using namespace std;

class Box
{
   public:
      static int objectCount; // 静态成员

      // 构造函数定义:长,宽,高
      Box(double l=1.0, double w=2.0, double h=3.0)
      {
         length = l;
         width = w;
         height = h;
      }

      // 静态成员函数
      static int getCreatCount()
      {
         // return objectCount + length; 错误,不能在静态成员函数里使用变量,非静态的
         return objectCount;
      }
   private:
      double length;     //长
      double width;    //宽
      double height;     //高
};

// 初始化类 Box 的静态成员
int Box::objectCount = 0;

int main(void)
{

   cout << "程序首次初始化Box对象前的对象数: " << Box::getCount() << endl; // 通过类名访问,而非对象访问

   Box Box1(4.5, 6.7, 8.9);    // 声明 box1
   Box Box2(3.5, 4.0, 8.0);    // 声明 box2

   cout << "创建过对象之后,通过类获取创建过的对象总数:" << Box::getCount() << endl;

   return 0;
}

# 二、继承

# 继承

继承应该是再熟悉不过的了,父亲立遗嘱让孩子继承他的财产和房子,

那么父亲就称为基类(父类)

而继承者称为派生类(子类)

而被继承的数据成员,和成员函数是(财产和房子)

面向对象程序设计中例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。

所示:

#include <iostream>
 
using namespace std;

// 基类(父类)
class Shape 
{
   public:
      void setWidth(int w)
      {
         width = w;
      }
      void setHeight(int h)
      {
         height = h;
      }
   protected:
      int width;
      int height;
};

// 派生类(子类)
class Rectangle: public Shape
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
};

int main(void)
{
   Rectangle Rect;
 
   Rect.setWidth(5);
   Rect.setHeight(7);

   cout << "面积: " << Rect.getArea() << endl;

   return 0;
} 

继承访问权限如下所示:

访问 public protected private
同一个类 yes yes yes
派生类 yes yes(可以继承) no
外部的类 yes no no

不能被继承的:

  • 基类的构造函数、析构函数和拷贝构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

# 多继承

多继承即一个子类可以有多个父类,它继承了多个父类的特性。(可惜,这在OC中确是不允许的)

C++ 类可以从多个类继承成员,各个基类之间用逗号分隔

如下所示:

#include <iostream>
 
using namespace std;

// 基类 Shape
class Shape 
{
   public:
      void setWidth(int w)
      {
         width = w;
      }
      void setHeight(int h)
      {
         height = h;
      }
   protected:
      int width;
      int height;
};

// 基类 PaintCost 
class PaintCost 
{
   public:
      int getCost(int area)
      {
         return area * 70;
      }
};

// 派生类,同时继承 Shape 和 PaintCost
class Rectangle: public Shape, public PaintCost
{
   public:
      int getArea()
      { 
         return (width * height); 
      }
};

int main(void)
{
   Rectangle Rect;
   int area;
 
   Rect.setWidth(5);
   Rect.setHeight(7);

   area = Rect.getArea();
   
   // 输出对象的面积
   cout << "Total area: " << Rect.getArea() << endl;

   // 输出总花费
   cout << "Total paint cost: $" << Rect.getCost(area) << endl;

   return 0;
}
C++语法速查
C++面向对象(下)

← C++语法速查 C++面向对象(下)→

Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式