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

  • Vue

  • C语法

  • C++语法

    • C++基础概念(上)
    • C++基础概念(下)
    • C++语法速查
    • C++面向对象(上)
    • C++面向对象(下)
    • C++高级教程(上)
    • C++高级教程(中)
      • 泛型编程
        • 函数模板
        • 模版函数-引用传参
        • 类成员函数-泛型
      • STL 标准模板(泛型)库
        • STL泛型库
        • map
        • vector
      • 预处理器
        • 预处理器
        • #define 预处理
        • 函数宏
        • 条件编译
        • # 和 ## 运算符
        • 预定义宏
    • C++高级教程(下)
  • 汇编语言

  • 软件编程及算法
  • C++语法
2023-05-31
目录

C++高级教程(中)

# 泛型编程

泛型编程的基础是模板,泛型编程即以一种独立于任何特定类型的方式编写代码。

同样在Swift中也支持泛型编程

但是在OC中是不支持泛型编程的,虽然不支持,但是OC的 id 能满足所有对象的通用性,一定程度也是实现了类似泛型能力,对数据类型是不支持的。

究其原因是因为,c/c++、Swift都是抢强型语言,

强类型语言必须一一匹配,否则编译阶段就会失败

而OC是弱类型语言,动态语言,所以其对对象甚至数据类型的匹配是模糊的,对数据类型的确认从编译阶段延迟到运行阶段,从而实现动态语言的特性。

# 函数模板

先看一个比较示例:

#include <iostream>
#include <string>

using namespace std;

class Person
{
   public:
     int age;
      Person(int a) { 
        age = a;
      }
};

int max(int a, int b) 
{
    return a > b ? a : b;
}

double max(double a, double b) 
{
    return a > b ? a : b;
}

string max(string a, string b) 
{
    return a > b ? a : b;
}

Person * max(Person a, Person b) 
{
    return a.age > b.age ? &a : &b;
}

int main ()
{
 
    cout << "int 类型比较大小 :max(3, 1) = " << max(3, 1) << endl; 
    cout << "double 类型比较大小 :max(3.88, 8.88) = " << max(3.88, 8.88) << endl; 
    cout << "string 类型比较大小 :max(a, d) = " << max("a", "d") << endl; 

    Person per1 = Person(20);
    Person per2 = Person(14);
    cout << "Person 类对象比较大小 :max(per1, per2) = " << max(per1, per2) << endl; 

   return 0;
}

结果

int 类型比较大小 :max(3, 1) = 3
double 类型比较大小 :max(3.88, 8.88) = 8.88
string 类型比较大小 :max(a, d) = a
Person 类对象比较大小 :max(per1, per2) = 0x7ffc59264e9c

小结:

我们发现上面示例中比较不同类型的大小方法存在很多通用型,假如将相识的实现函数通过定义一个通用模版函数来实现,那么是否会更简单呢?

模板函数定义:

/// template : 模版声明关键字
/// type 只是一个占位符名称,在函数定义中使用
/// func-name 模版方法名称
/// list 模版函数参数
template <typename type> ret-type func-name(parameter list)
{
   // 函数的主体
}  

下面通过一个示例的演变来解释泛型概念

将前面的示例进行优化:

#include <iostream>
#include <string>

using namespace std;

class Person
{
   public:
     int age;
      Person(int a) { 
        age = a;
      }
      // 重载 < 运算符
	    bool operator<(const Person& p)
      {
         return this->age < p.age ? true : false; 
      }
};

template <typename T> inline T const& Max (T a, T b) // const& 
{ 
    return a < b ? b : a; 
} 

int main ()
{
	
    cout << "int 类型比较大小 :Max(3, 1) = " << Max(3, 1) << endl; 
    cout << "double 类型比较大小 :max(3.88, 8.88) = " << Max(3.88, 8.88) << endl; 
    cout << "string 类型比较大小 :max(a, d) = " << Max("a", "d") << endl; 

    Person per1 = Person(20);
    Person per2 = Person(14);
    Person max_p = Max(per1, per2);
    cout << "Person 类对象比较大小 :max(per1, per2) 的 age = " << max_p.age << endl; 

   return 0;
}

结果:

int 类型比较大小 :Max(3, 1) = 3
double 类型比较大小 :max(3.88, 8.88) = 8.88
string 类型比较大小 :max(a, d) = a
Person 类对象比较大小 :max(per1, per2) 的 age = 20

⚠️ 上面示例中我们泛型没有限定,如果参数是对象,那么返回的也是对象,如果是普通的函数这是不允许的,因为通常函数只能返回对象的内存地址引用,而不能返回对象变量

我将上述示例打印对象参数前后的内存地址:

#include <iostream>
#include <string>

using namespace std;

class Person
{
   public:
     int age;
      Person(int a) { 
        age = a;
      }
      // 重载 < 运算符
	    bool operator<(const Person& p)
      {
         return this->age < p.age ? true : false; 
      }
};

template <typename T> inline T const& Max (T a, T b) // const& 
{ 
	cout << "第一个对象参数地址 = " << &a << endl; 
	cout << "第二个对象参数地址 = " << &b << endl; 
	
    return a < b ? b : a; 
} 

int main ()
{
    Person per1 = Person(20);
    Person per2 = Person(14);
	
	cout << "第一个对象地址 = " << &per1 << endl; 
	cout << "第二个对象地址 = " << &per2 << endl; 
	
    Person max_p = Max(per1, per2);
    cout << "max(per1, per2) 的 age = " << max_p.age << endl; 

   return 0;
}

结果

第一个对象地址 = 0x7ffdc02eca3c
第二个对象地址 = 0x7ffdc02eca38

第一个对象参数地址 = 0x7ffdc02eca1c
第二个对象参数地址 = 0x7ffdc02eca18
max(per1, per2) 的 age = 20

结果发现,直接传入对象变量时,对象的内存地址是不一样的(直接拿参数对象修改,函数外定义的对象不受影响),其实C++中是将这些对象都复制了一份,

称其为【副本】,后面解释什么是副本。

# 模版函数-引用传参

如果想传入参数是对象本身(在函数内修改,函数外定义的对象同时被修改),那么需要使用内存地址引用传值

如果是内存地址对象数据本身比较,可以设置参数是地址:

template <typename T> inline T const& Max (T const& a, T const& b) 
{ 
    return a < b ? b:a; 
}

# 类成员函数-泛型

类成员函数模板-泛型

泛型类声明的一般形式如下所示:

template <class type> class class-name {

}

栈:

入栈出栈操作示例:

⚠️⚠️ ⚠️ 必须要注意的一点:vector 操作需要使用副本 ,否则取值和修改都是不对的

#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>

using namespace std;

template <class T>
class Stack { 
  private: 
    vector<T> elems;     // 元素 

  public: 
    void push(T const&);  		// 压栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真。
        return elems.empty(); 
    } 
}; 

template <class T>
void Stack<T>::push (T const& elem) 
{ 
    // 压栈(使用副本)
    elems.push_back(elem);
} 

template <class T>
void Stack<T>::pop () 
{ 
    if (elems.empty()) { 
        throw out_of_range("出栈异常 Stack<>::pop(): empty stack"); 
    }
	cout << "出栈元素:" << elems.back() <<endl; 
	// 出栈(使用副本)
    elems.pop_back();         
} 

template <class T>
T Stack<T>::top () const 
{ 
    if (elems.empty()) { 
        throw out_of_range("Stack<>::top(): empty stack"); 
    }
	  // 栈顶元素(副本) 
    return elems.back();      
} 

int main() 
{ 
    try { 
        // int 类型的栈
        Stack<int> intStack; 
        // 压栈
        intStack.push(1); 
        intStack.push(5); 
      	intStack.push(9); 
        cout << "intStack 栈顶元素:" << intStack.top() <<endl; 

      
        // string 类型的栈 
        Stack<string> stringStack;    
        // 压栈
        stringStack.push("one"); 
        stringStack.push("five"); 
        stringStack.push("nine"); 
        cout << "stringStack 栈顶元素:" << stringStack.top() << std::endl; 
        
        stringStack.pop(); 
        stringStack.pop(); 
        stringStack.pop(); 
        stringStack.pop(); // 越界异常
    } 
    catch (exception const& ex) { 
        cout << "Exception: " << ex.what() <<endl; 
    } 
}  

结果:

intStack 栈顶元素:9

stringStack 栈顶元素:nine
出栈元素:nine
出栈元素:five
出栈元素:one
Exception: 出栈异常 Stack<>::pop(): empty stack

# STL 标准模板(泛型)库

标准库查询:

https://www.apiref.com/cpp-zh/cpp/header.html

STL

STL可参考链接1 (opens new window)

STL可参考链接2 (opens new window)

简单理解就是对一系列方法和算法函数进行封装,并使用模版函数(泛型)的方式,创建一个标准的功能库,称STL

# STL泛型库

C++ 对模板(Template)支持得很好,STL 就是借助模板把常用的数据结构及其算法都实现了一遍,并且做到了数据结构和算法的分离。

例如,vector 的底层为顺序表(数组),list 的底层为双向链表,deque 的底层为循环队列,set 的底层为红黑树,hash_set 的底层为哈希表。

通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成,其中后面 4 部分是为前 2 部分服务的,它们各自的含义如表 1 所示。

STL的组成 含义
容器 一些封装数据结构的模板类,例如 vector 向量容器、list 列表容器等。
算法 STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义,其中大部分算法都包含在头文件 algorithm 中,少部分位于头文件 numeric 中。
迭代器 在 C++ STL 中,对容器中数据的读和写,是通过迭代器完成的,扮演着容器和算法之间的胶合剂。
函数对象 如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称仿函数)。
适配器 可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。值得一提的是,容器、迭代器和函数都有适配器。
内存分配器 为容器类模板提供自定义的内存申请和释放功能,由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。

# map

用于存储和检索集合(类似array) 中的数据,此集合中的每个元素均为包含数据值和排序键的元素对。 键的值是唯一的,用于自动排序数据。

可以直接更改映射中的元素值。 键值是常量,不能更改。 必须先删除与旧元素关联的键值,才能为新元素插入新键值。

# map构造函数
名称 说明
map (opens new window) 构造特定大小的列表、包含具有特定值的元素的列表、包含特定 allocator 的列表或作为其他某个映射的副本的列表。
# map的Typedef
名称 说明
allocator_type (opens new window) 映射对象的 allocator 类的 typedef。
const_iterator (opens new window) 可读取 const 中 map 元素的双向迭代器的 typedef。
const_pointer (opens new window) 指向映射中的 const 元素的指针的 typedef。
const_reference (opens new window) 对存储在映射中的 const 元素的引用(用于读取和执行 const 操作)的 typedef。
const_reverse_iterator (opens new window) 一种类型,此类型提供可读取 const 中的任何 map 元素的双向迭代器。
difference_type (opens new window) 映射中迭代器指向的元素间范围内元素数量的有符号整数 typedef。
iterator (opens new window) 可读取或修改映射中任何元素的双向迭代器的 typedef。
key_compare (opens new window) 可比较两个排序键以确定 map 中两个元素的相对顺序的函数对象的 typedef。
key_type (opens new window) 存储在映射内每个元素中的排序键的 typedef。
mapped_type (opens new window) 存储在映射内每个元素中的数据的 typedef。
pointer (opens new window) 指向映射中的 const 元素的指针的 typedef。
reference (opens new window) 对映射中存储的元素的引用的 typedef。
reverse_iterator (opens new window) 可读取或修改反向映射中的元素的双向迭代器的 typedef。
size_type (opens new window) 映射中元素数量的无符号整数 typedef。
value_type (opens new window) 作为元素存储在映射中的对象类型的 typedef。
# map成员函数
成员函数 说明
at (opens new window) 查找具有指定键值的元素。
begin (opens new window) 返回一个迭代器,此迭代器指向 map 中的第一个元素。
cbegin (opens new window) 返回一个常量迭代器,此迭代器指向 map 中的第一个元素。
cend (opens new window) 返回一个超过末尾常量迭代器。
clear (opens new window) 清除 map 的所有元素。
contains (opens new window)C++20 检查 map 中是否包含具有指定键的元素。
count (opens new window) 返回映射中其键与参数中指定的键匹配的元素数量。
crbegin (opens new window) 返回一个常量迭代器,此迭代器指向反向 map 中的第一个元素。
crend (opens new window) 返回一个常量迭代器,此迭代器指向反向 map 中最后一个元素之后的位置。
emplace (opens new window) 将就地构造的元素插入到 map。
emplace_hint (opens new window) 将就地构造的元素插入到 map,附带位置提示。
empty (opens new window) 如果 map 为空,则返回 true。
end (opens new window) 返回超过末尾迭代器。
equal_range (opens new window) 返回一对迭代器。 此迭代器对中的第一个迭代器指向 map 中其键大于指定键的第一个元素。 此迭代器对中的第二个迭代器指向 map 中其键等于或大于指定键的第一个元素。
erase (opens new window) 从指定位置移除映射中的元素或元素范围。
find (opens new window) 返回一个迭代器,此迭代器指向 map 中其键与指定键相等的元素的位置。
get_allocator (opens new window) 返回用于构造 allocator 的 map 对象的副本。
insert (opens new window) 将一个或一系列元素插入到 map 中的指定位置。
key_comp (opens new window) 返回用于对 map 中的键进行排序的比较对象副本。
lower_bound (opens new window) 返回一个迭代器,此迭代器指向 map 中其键值等于或大于指定键的键值的第一个元素。
max_size (opens new window) 返回 map 的最大长度。
rbegin (opens new window) 返回一个迭代器,此迭代器指向反向 map 中的第一个元素。
rend (opens new window) 返回一个迭代器,此迭代器指向反向 map 中最后一个元素之后的位置。
size (opens new window) 返回 map 中的元素数量。
swap (opens new window) 交换两个映射的元素。
upper_bound (opens new window) 返回一个迭代器,此迭代器指向 map 中其键值大于指定键的键值的第一个元素。
value_comp (opens new window) 检索用于对 map 中的元素值进行排序的比较对象副本。
# map运算符
名称 说明
operator[\] (opens new window) 将元素插入到具有指定键值的映射。
operator= (opens new window) 将一个映射中的元素替换为另一映射副本。

# vector

相比 array 容器,vector 提供了更多了成员函数供我们使用,如表 1 所示:

函数成员 函数功能
begin() 返回指向容器中第一个元素的迭代器。
end() 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
rbegin() 返回指向最后一个元素的迭代器。
rend() 返回指向第一个元素所在位置前一个位置的迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size() 返回实际元素个数。
max_size() 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
resize() 改变实际元素的个数。
capacity() 返回当前容量。
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
reserve() 增加容器的容量。
shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
operator[ ] 重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。
at() 使用经过边界检查的索引访问元素。
front() 返回第一个元素的引用。
back() 返回最后一个元素的引用。
data() 返回指向容器中第一个元素的指针。
assign() 用新元素替换原有内容。
push_back() 在序列的尾部添加一个元素。
pop_back() 移出序列尾部的元素。
insert() 在指定的位置插入一个或多个元素。
erase() 移出一个元素或一段元素。
clear() 移出所有的元素,容器大小变为 0。
swap() 交换两个容器的所有元素。
emplace() 在指定的位置直接生成一个元素。
emplace_back() 在序列尾部生成一个元素。

# 预处理器

预处理器就是将相同常量、逻辑代码、代码块等等抽出供使用。

宏不同于函数,它提供方便,但是不会减少代码量,因为在编译的时候会将代码重复的复制到使用的每一个地方

前面在c语言也有介绍。这里简单列举

# 预处理器

以井号(#)开头

比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。

# #define 预处理

#include <iostream>
using namespace std;

#define kPI 3.14

int main ()
{
    cout << "半径5的面积约 = " << kPI*5*5 << endl; 

    return 0;
}
半径5的面积约 = 78.5

# 函数宏

#include <iostream>
using namespace std;

#define Max(a,b) (((a)>=(b)) ? a : b)

int main ()
{
    cout << "5和8的最大值 = " << Max(5, 8) << endl;

    return 0;
}

结果:

5和8的最大值 = 8

# 条件编译

if条件编译宏

// ifndef 判断如果没有定义过,那么开始定义
#ifndef AUTO
   #define AUTO 0
#endif


#ifdef DEBUG
   // 调试模式的代码
#else
	 // 其他,或者生产环境代码
#endif 

# # 和 ## 运算符

1、宏定义中【 # 运算符】为内容添加为被引号的字符串,比如直接打印枚举名称,而不是显示对应的value:

#include <iostream>
using namespace std;

enum RGB{
	red, green, yellow
};

// 将参数“名称”内容直接替换成对应字符串
#define MKSTR(x) #x

int main ()
{
    cout << MKSTR(RGB::green) << endl;
    cout << RGB::green << endl;
    return 0;
} 

结果:

RGB::green
1

2、宏定义中【## 运算符】用于连接两个令牌,这很有用,你可以将两个名称拼接成一个变量名称

实例:

#define CONCAT( x, y )  x ## y

实例:

#include <iostream>
using namespace std;

#define concat(a, b) a ## b
int main()
{
   int xy = 98;
   
   cout << concat(x, y); // 等同于将x、y拼接成变量 xy 使用
   return 0;
}

结果:

98

# 预定义宏

预定义宏:

宏 描述
LINE 这会在程序编译时包含当前行号。
FILE 这会在程序编译时包含当前文件名。
DATE 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。
TIME 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。

让我们看看上述这些宏的实例:

#include <iostream>
using namespace std;

int main ()
{
	
    cout << "当前代码行 : " << __LINE__ << endl;
		cout << "当前函数名称 : " << __FUNCTION__ << endl;
    cout << "当前文件名称 : " << __FILE__ << endl;
	
    cout << "当前年月日 : " << __DATE__ << endl;
    cout << "当前时分秒 : " << __TIME__ << endl;

    return 0;
}

结果:

当前代码行 : 7
当前代码行 : main
当前文件名称 : main.cpp
当前年月日 : Jul  6 2023
当前时分秒 : 11:01:35
C++高级教程(上)
C++高级教程(下)

← C++高级教程(上) C++高级教程(下)→

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