C++学习总结
本文发布于590天前,本文最后更新于590 天前,其中的信息可能已经过时,如有错误请留言或评论。

小tips

  1. c++限制小数的位数

     #include < iomanip >
     
     //主要是对cin,cout之类的一些操纵运算子,比如setfill,setw,setbase,setprecision等等。
     //它是I/O流控制头文件,就像C里面的格式化输出一样.以下是一些常见的控制函数的:dec 置基数为10 相当 于"%d"hex 置基数为16 相当于"%X"oct 置基数为8 相当于"%o"setfill© 设填充字符为csetprecision(n) 设显
     //示小数精度为n位setw(n) 设域宽为n个字符这个控制符的意思是保证输出宽度为n。如:
     cout<<setw(3)<<1<<setw(3)<<10<<setw(3)<<100; 输出结果为1 10100 (默认是右对齐)当输出长度大于3时(<<1000) setw(3)不起作用。setioflags(ios::fixed)
     //固定的浮点显示…
     
     例如:
     
     const double value = 12.3456789;
     cout << value << endl; // 默认以6精度,所以输出为 12.3457 cout << setprecision(4) << value << endl; // 改成4精度,所以输出为12.35 cout <<
     setprecision(8) << value << endl; // 改成8精度,所以输出为12.345679 cout <<
     fixed << setprecision(4) << value << endl; //
     //加了fixed意味着是固定点方式显示,所以这里的精度指的是小数位,输出为12.3457 cout << value << endl; //
     
  2. =初始化类调用的是拷贝构造函数

c++中

vector详解

1. 头文件

 #include<vector>

2. vector声明及初始化

 vector<int> vec;        //声明一个int型向量
 vector<int> vec(5);     //声明一个初始大小为5的int向量
 vector<int> vec(10, 1); //声明一个初始大小为10且值都是1的向量
 vector<int> vec(tmp);   //声明并用tmp向量初始化vec向量
 vector<int> tmp(vec.begin(), vec.begin() + 3);  //用向量vec的第0个到第2个值初始化tmp
 int arr[5] = {1, 2, 3, 4, 5};   
 vector<int> vec(arr, arr + 5);      //将arr数组的元素用于初始化vec向量
 //说明:当然不包括arr[4]元素,末尾指针都是指结束元素的下一个元素,
 //这个主要是为了和vec.end()指针统一。
 vector<int> vec(&arr[1], &arr[4]); //将arr[1]~arr[4]范围内的元素作为vec的初始值

3. vector基本操作

(1). 容量

  • 向量大小: vec.size();

  • 向量最大容量: vec.max_size();

  • 更改向量大小: vec.resize();

  • 向量真实大小: vec.capacity();

  • 向量判空: vec.empty();

  • 减少向量大小到满足元素所占存储空间的大小: vec.shrink_to_fit(); //shrink_to_fit

(2). 修改

  • 多个元素赋值: vec.assign(); //类似于初始化时用数组进行赋值

  • 末尾添加元素: vec.push_back();

  • 末尾删除元素: vec.pop_back();

  • 任意位置插入元素: vec.insert();

  • 任意位置删除元素: vec.erase();

  • 交换两个向量的元素: vec.swap();

  • 清空向量元素: vec.clear();

(3)迭代器

  • 开始指针:vec.begin();

  • 末尾指针:vec.end(); //指向最后一个元素的下一个位置

  • 指向常量的开始指针: vec.cbegin(); //意思就是不能通过这个指针来修改所指的内容,但还是可以通过其他方式修改的,而且指针也是可以移动的。

  • 指向常量的末尾指针: vec.cend();

(4)元素的访问

  • 下标访问: vec[1]; //并不会检查是否越界

  • at方法访问: vec.at(1); //以上两者的区别就是at会检查是否越界,是则抛出out of range异常

  • 访问第一个元素: vec.front();

  • 访问最后一个元素: vec.back();

  • 返回一个指针: int* p = vec.data(); //可行的原因在于vector在内存中就是一个连续存储的数组,所以可以返回一个指针指向这个数组。这是是C++11的特性。

4. 算法

  • 遍历元素

 vector<int>::iterator it;
 for (it = vec.begin(); it != vec.end(); it++)
     cout << *it << endl;
 //或者
 for (size_t i = 0; i < vec.size(); i++) {
     cout << vec.at(i) << endl;
 }
  • 元素翻转

 #include <algorithm>
 reverse(vec.begin(), vec.end());
  • 元素排序

 #include <algorithm>
 sort(vec.begin(), vec.end()); //采用的是从小到大的排序
 //如果想从大到小排序,可以采用上面反转函数,也可以采用下面方法:
 bool Comp(const int& a, const int& b) {
     return a > b;
 }
 sort(vec.begin(), vec.end(), Comp);

c++中map详解

map简介

map是STL的一个关联容器,以键值对存储的数据,其类型可以自己定义,每个关键字在map中只能出现一次,关键字不能修改,值可以修改;map同set、multiset、multimap(与map的差别仅在于multimap允许一个键对应多个值)内部数据结构都是红黑树,而java中的hashmap是以hash table实现的。所以map内部有序(自动排序,单词时按照字母序排序),查找时间复杂度为O(logn)。

map用法

1、头文件

 #include<map>

2、定义

 map<string,int> my_map;
 也可以使用
 typedef map<string,int> My_Map;
 My_Map my_map;

3、基本方法

4、map插入数据的几种方法

 //第一种:用insert函数插入pair数据:
 map<int,string> my_map;
 my_map.insert(pair<int,string>(1,"first"));
 my_map.insert(pair<int,string>(2,"second"));
 //第二种:用insert函数插入value_type数据:
 map<int,string> my_map;
 my_map.insert(map<int,string>::value_type(1,"first"));
 my_map.insert(map<int,string>::value_type(2,"second"));
 
 map<int,string>::iterator it;           //迭代器遍历
 for(it=my_map.begin();it!=my_map.end();it++)
     cout<<it->first<<it->second<<endl;
 //第三种:用数组的方式直接赋值:
 map<int,string> my_map;
 my_map[1]="first";
 my_map[2]="second";
 
 map<int,string>::iterator it;
 for(it=my_map.begin();it!=my_map.end();it++)
     cout<<it->first<<it->second<<endl;

5、查找元素(判定这个关键字是否在map中出现)

第一种:用count函数来判断关键字是否出现,其缺点是无法定位元素出现的位置。由于map一对一的映射关系,count函数的返回值要么是0,要么是1。

 map<string,int> my_map;
 my_map["first"]=1;
 cout<<my_map.count("first")<<endl;    //输出1;

第二种:用find函数来定位元素出现的位置,它返回一个迭代器,当数据出现时,返回的是数据所在位置的迭代器;若map中没有要查找的数据,返回的迭代器等于end函数返回的迭代器。

 #include <map>  
 #include <string>  
 #include <iostream>  
 
 using namespace std;  
 
 int main()  
 {  
     map<int, string> my_map;  
     my_map.insert(pair<int, string>(1, "student_one"));  
     my_map.insert(pair<int, string>(2, "student_two"));  
     my_map.insert(pair<int, string>(3, "student_three"));  
     map<int, string>::iterator it;  
     it = my_map.find(1);  
     if(it != my_map.end())  
        cout<<"Find, the value is "<<it->second<<endl;      
     else  
        cout<<"Do not Find"<<endl;  
     return 0;  
 }
 //通过map对象的方法获取的iterator数据类型是一个std::pair对象,包括两个数据iterator->first和iterator->second,分别代表关键字和value值。

6、删除元素

 #include <map>  
 #include <string>  
 #include <iostream>  
 
 using namespace std;  
 
 int main()  
 {  
     map<int, string> my_map;  
     my_map.insert(pair<int, string>(1, "one"));  
     my_map.insert(pair<int, string>(2, "two"));  
     my_map.insert(pair<int, string>(3, "three"));  
     //如果你要演示输出效果,请选择以下的一种,你看到的效果会比较好
     //如果要删除1,用迭代器删除
     map<int, string>::iterator it;  
     it = my_map.find(1);  
     my_map.erase(it);                   //如果要删除1,用关键字删除
     int n = my_map.erase(1);            //如果删除了会返回1,否则返回0
     //用迭代器,成片的删除
     //一下代码把整个map清空
     my_map.erase( my_map.begin(), my_map.end() );  
     //成片删除要注意的是,也是STL的特性,删除区间是一个前闭后开的集合
     //自个加上遍历代码,打印输出吧
     return 0;
 }  

7、排序,按value排序

map中元素是自动按key升序排序(从小到大)的;按照value排序时,想直接使用sort函数是做不到的,sort函数只支持数组、vector、list、queue等的排序,无法对map排序,那么就需要把map放在vector中,再对vector进行排序。

 #include <iostream>
 #include <string>
 #include <map>
 #include <algorithm>
 #include <vector>
 using namespace std;
 
 bool cmp(pair<string,int> a, pair<string,int> b) {
     return a.second < b.second;
 }
 
 int main()
 {
     map<string, int> ma;
     ma["Alice"] = 86;
     ma["Bob"] = 78;
     ma["Zip"] = 92;
     ma["Stdevn"] = 88;
     vector< pair<string,int> > vec(ma.begin(),ma.end());
     //或者:
     //vector< pair<string,int> > vec;
     //for(map<string,int>::iterator it = ma.begin(); it != ma.end(); it++)
     //    vec.push_back( pair<string,int>(it->first,it->second) );
 
     sort(vec.begin(),vec.end(),cmp);
     for (vector< pair<string,int> >::iterator it = vec.begin(); it != vec.end(); ++it)
     {
         cout << it->first << " " << it->second << endl;
     }
     return 0;
 
 }

函数的调用可以作为左值

如果函数的返回值是引用,这个函数调用可以作为左值。

 #include<iostream>
 using spacename std;
 
 int &test()
 {
     static int a = 10;
     return a;
 }
 
 int main()
 {
     int &ref = test();
     cout << ref << endl;    //10
     cout << ref << endl;    //10
     test() = 1000;
     cout << ref << endl;    //1000
     cout << ref << endl;    //1000
     return 0;
 }

引用的本质

引用必须引用一块合法的内存空间

结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了

函数提高

函数默认参数

注意:

  1. 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值

  2. 如果函数声明有默认参数,函数实现就不能有默认参数,如果函数实现有默认参数,函数声明就不能有默认参数,即声明和实现只能有一个有默认参数。

函数的占位参数

函数重载

可以让函数名称相同,提高复用性

需满足条件:

  1. 同一个作用域下

  2. 函数名称相同

  3. 函数参数类型不同,或者个数不同,或者顺序不同

注意函数的返回值不能作为函数重载的条件

类和对象

C++面向对象的三大特性为:封装、继承、多态

封装的权限控制

struct和class的区别

struct 默认权限是 共有 public class 默认权限是 私有 private

成员属性设置为私有

  1. 可以自己控制读写权限

  2. 对于写可以检测数据的有效性

运算符重载

+-运算符重载

 #include<iostream>
 using namespace std;
 class Person 
 {
 public:
     ////成员函数重载
     //Person operator+(Person& p)
     //{
     //  Person temp;
     //  temp.m_a = this->m_a + p.m_a;
     //  temp.m_b = this->m_b + p.m_b;
     //  return temp;
     //}
     int m_a;
     int m_b;
 };
 
 //全局函数重载
 Person operator+(Person& p1, Person& p2)
 {
     Person temp;
     temp.m_a = p1.m_a + p2.m_a;
     temp.m_b = p1.m_b + p2.m_b;
     return temp;
 }
 //函数重载的版本
 Person operator+(Person& p1, int num)
 {
     Person temp;
     temp.m_a = p1.m_a + num;
     temp.m_b = p1.m_b + num;
     return temp;
 }
 
 void test01()
 {
     Person p1;
     p1.m_a = 10;
     p1.m_b = 10;
     Person p2;
     p2.m_a = 10;
     p2.m_b = 10;
     //成员函数的本质调用
     //Person p3 = p1.operator+(p2);
     //全局函数的本质调用
     //Person p3 = operator(p1, p2);
     Person p3 = p1 + p2;
     Person p4 = p1 + 100;
     cout << "p3.m_a = " << p3.m_a << endl;
     cout << "p3.m_b = " << p3.m_b << endl;
 
     cout << "p4.m_a = " << p4.m_a << endl;
     cout << "p4.m_b = " << p4.m_b << endl;
 }
 int main()
 {
     test01();
 }

递增运算符重载

 #include<iostream>
 using namespace std;
 class Myint
 {
 public:
     friend ostream& operator<<(ostream& cout, Myint myint);
     Myint(int a):m_num(a) {}
     //重载前置++运算符
     Myint &operator++()
     {
         //先进行递增操作
         m_num++;
         //在将
         return *this;
     }
     //重载后置++运算符
     Myint operator++(int) //int代表占位参数,可以用于区分前置和后置递增
     {
         //先 记录当前数值
         Myint temp = *this;
         //后 递增
         m_num++;
         //最后将记录结果做返回
         return temp;
     }
 private:
     int m_num;
 };
 //重载左移运算符
 ostream& operator<<(ostream& cout, Myint myint)
 {
     cout << myint.m_num;
     return cout;
 }
 
 //void test01()
 //{
 //  Myint myint(10);
 //  cout << ++myint << endl;
 //}
 void test02()
 {
     Myint myint(10);
     cout << myint++ << endl;
     cout << myint << endl;
 }
 int main()
 {
     //test01();
     test02();
 }

等号运算符重载(默认等号为浅拷贝)

QQ截图20230504201137

QQ截图20230504201137

 #include<iostream>
 using namespace std;
 
 class Person
 {
 public:
     Person(int age)
     {
         m_age = new int(age);
     }
     ~Person()
     {
         if (m_age != NULL)
         {
             delete m_age;
             m_age = NULL;
         }
     }
 
 
     //重载等号运算符
     Person & operator=(Person &p)
     {
         //编译器提供浅拷贝
         //m_age = p.m_age
 
         //应该先判断是否有属性在堆区,如果有,先释放干净,如何在深拷贝
         if (m_age != NULL)
         {
             delete m_age;
             m_age = NULL;
         }
         //深拷贝
         m_age = new int(*p.m_age);
         //返回对象本身
         return *this;
     }
     int* m_age;
 };
 
 void test01()
 {
     Person p1(18);
     Person p2(20);
     Person p3(30);
     p3 = p2 = p1;//赋值操作
     cout << "p1的年龄为为: " << *p1.m_age << endl;
     cout << "p2的年龄为为: "<< *p2.m_age << endl;
     cout << "p3的年龄为为: " << *p3.m_age << endl;
 }
 
 int main()
 {
     test01();
 
     //int a = 10;
     //int b = 20;
     //int c = 30;
     //c = b = a;
     //cout << "a=" << a << endl;
     //cout << "b=" << b << endl;
     //cout << "c=" << c << endl;
 }

关系运算符重载

 #include<iostream>
 #include<string.h>
 using namespace std;
 
 class Person
 {
 public:
     Person(string name,int age):m_name(name),m_age(age){}
     
     bool operator==(Person& p)
     {
         if (this->m_name == p.m_name && this->m_age == p.m_age)
         {
             return true;
         }
         return false;
     }
     bool operator!=(Person& p)
     {
         if (this->m_name == p.m_name && this->m_age == p.m_age)
         {
             return false;
         }
         return true;
     }
     
     string m_name;
     int m_age;
 };
 
 void test01()
 {
     Person p1("Tom", 18);
     Person p2("Tom", 18);
     if (p1 == p2)
     {
         cout << "p1 p2 是相等的!" << endl;
     }
     else
     {
         cout << "p1 p2是不相等的!" << endl;
     }
     if (p1 != p2)
     {
         cout << "p1 p2是不相等的!" << endl;
     }
     else
     {
         cout << "p1 p2 是相等的!" << endl;
 
     }
 }
 
 int main()
 {
     test01();
     return 0;
 }

函数调用运算符重载(仿函数)

 #include<iostream>
 #include<string.h>
 using namespace std;
 
 //函数调用运算符重载
 
 
 class Myprint
 {
 public:
     void operator()(string test)
     {
         cout << test << endl;
     }
 };
 
 void Myprint02(string test)
 {
     cout << test << endl;
 }
 
 void test01()
 {
     Myprint myprint;
     myprint("Hello world!!!");//由于使用起来非常类似与函数调用,因此称为仿函数
 
     Myprint02("Hello world!!!");
 }
 //仿函数非常灵活,没有固定写法
 //加法类
 class Myadd
 {
 public:
     int operator()(int num1, int num2)
     {
         return num1 + num2;
     }
 };
 
 void test02()
 {
     Myadd myadd;
     int ret = myadd(100, 100);
     cout << ret << endl;
 
     //匿名函数对象
     cout << Myadd()(100, 100) << endl;
 }
 
 int main()
 {
     //test01();
     test02();
 }

左移运算符重载

 #include<iostream>
 using namespace std;
 class Person
 {
 public:
     int m_a;
     int m_b;
 };
 ostream &operator<<(ostream& cout, Person& p)
 {
     cout << "m_a = " << p.m_a << " m_b = " << p.m_b ;
     return cout;
 }
 void test01()
 {
     Person p;
     p.m_a = 10;
     p.m_b = 10;
     cout << p << endl;;
 }
 int main()
 {
     test01();
 
 }

继承

继承的基本语法

普通实现
 #include<iostream>
 using namespace std;
 
 // 普通实现页面
 
 // Jave页面
 class Java
 {
 public:
     void header()
     {
         cout << "首页 公开课 登录 注册 " << endl;
     }
     void footer()
     {
         cout << "帮助中心 合作交流 站内地图" << endl;
     }
     void left()
     {
         cout << "Jave Pyton C++  " << endl;
     }
     void content()
     {
         cout << "Jave 学科视频" << endl;
     }
 };
 
 class Python
 {
 public:
     void header()
     {
         cout << "首页 公开课 登录 注册 " << endl;
     }
     void footer()
     {
         cout << "帮助中心 合作交流 站内地图" << endl;
     }
     void left()
     {
         cout << "Jave Pyton C++  " << endl;
     }
     void content()
     {
         cout << "Python 学科视频" << endl;
     }
 };
 
 class Cpp
 {
 public:
     void header()
     {
         cout << "首页 公开课 登录 注册 " << endl;
     }
     void footer()
     {
         cout << "帮助中心 合作交流 站内地图" << endl;
     }
     void left()
     {
         cout << "Jave Pyton C++  " << endl;
     }
     void content()
     {
         cout << "C++ 学科视频" << endl;
     }
 };
 
 void test01()
 {
     cout << "Jave 下载视频页面如下" << endl;
     Java ja;
     ja.header();
     ja.footer();
     ja.left();
     ja.content();
 
     cout << "--------------------------------" << endl;
     cout << "Python 下载视频页面如下" << endl;
     Python py;
     py.header();
     py.footer();
     py.left();
     py.content();
 
     cout << "--------------------------------" << endl;
     cout << "C++ 下载视频页面如下" << endl;
     Cpp cp;
     cp.header();
     cp.footer();
     cp.left();
     cp.content();
 }
 
 int main()
 {
     test01();
     return 0;
 }
继承实现
#include<iostream>
using namespace std;

// 继承实现页面

class BasePage
{
public:
	void header()
	{
		cout << "首页 公开课 登录 注册 " << endl;
	}
	void footer()
	{
		cout << "帮助中心 合作交流 站内地图" << endl;
	}
	void left()
	{
		cout << "Jave Pyton C++  " << endl;
	}
};

// Jave页面
class Java:public BasePage
{
public:
	void content()
	{
		cout << "Jave 学科视频" << endl;
	}
};

class Python :public BasePage
{
public:
	void content()
	{
		cout << "Python 学科视频" << endl;
	}
};

class Cpp :public BasePage
{
public:
	void content()
	{
		cout << "C++ 学科视频" << endl;
	}
};

void test01()
{
	cout << "Jave 下载视频页面如下" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();

	cout << "--------------------------------" << endl;
	cout << "Python 下载视频页面如下" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();

	cout << "--------------------------------" << endl;
	cout << "C++ 下载视频页面如下" << endl;
	Cpp cp;
	cp.header();
	cp.footer();
	cp.left();
	cp.content();
}

int main()
{
	test01();
	return 0;
}

QQ截图20230504201137

继承方式

公共 私有 保护

QQ截图20230504201137

继承中的对象模型

QQ截图20230504201137

继承中构造和析构顺序

先构造父类,在构造子类,析构顺序相反

继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员直接访问即可

  • 访问父类同名成员需要加作用域

  • 若子类中出现了和父类同名的成员函数,子类的同名函数会隐藏掉父类中所有同名函数,加作用域才可以访问到

继承中同名静态函数成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?

  • 静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员直接访问即可

  • 访问父类同名成员需要加作用域

多继承语法

C++允许一个类继承多个类

语法: class子类:继承方式 父类1, 继承方式 父类2...

多继承可能会引发父类中有同名成员出现,需要加作用域区分 ,C++实际开发中不建议用多继承

总结: 多继承中如果父类中出现了同名情况,子类使用时候要加作用域

菱形继承

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。

  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

#include<iostream>
using namespace std;


//动物类
class Animal
{
public:
	int age;
};

//利用虚继承,解决菱形继承的问题
//继承之前加关键字virtual 变为虚继承
//Animal类 称为虚基类

//羊类
class Sheep :virtual public Animal
{

};

//骆驼类
class Tuo :virtual public Animal
{

};

//羊驼类
class SheepTuo : public Sheep, public Tuo
{

};

void test01()
{
	SheepTuo St;
	St.Sheep::age = 18;
	St.Tuo::age = 38;
	//菱形继承两个父类拥有相同属性,需加作用域加以区分
	cout << "St.Sheep::age = " << St.Sheep::age << endl;
	cout << "St.Tuo::age = " << St.Tuo::age << endl;
	cout << "St.age = " << St.age << endl;
	//这份数据我们知道只有一份就可以,菱形继承导致数女接有两份,资源浪费



}

int main()
{
	test01();

//	system("pause");
	return 0;
}

QQ截图20230504201137

总结:

  1. 菱形继承带来的主要问题是好类继承两份相同的数据,导致资源浪费以及毫无意义

  2. 利用虚继承可以解决菱形继承问题,继承之前加关键字virtual 变为虚继承

多态

多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数格

  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址

  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

动态多态满足条件

  1. 有继承关系

  2. 子类重写父类的虚函数

    重写: 函数返回值类型 函数名 参数列表完全一致称为重写

动态多态使用:父类的指针或者引用,指向子类对象

#include<iostream>
using namespace std;

class Animal
{
public:
	//虚函数
	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat : public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};
class Dog : public Animal
{
public:
	void speak()
	{
		cout << "小狗在说话" << endl;
	}
};

//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void dospeak(Animal &animal) //Animal &animal =cat
{
	animal.speak();
}

void test01()
{
	Cat cat;
	dospeak(cat);

	Dog dog;
	dospeak(dog);
}
int main()
{
	test01();

//	system("pause");
	return 0;
}

QQ截图20230504201137

未重写时

QQ截图20230504201137

重写后

QQ截图20230504201137

多态案例-----计算器类

案例描述: 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰

  • 可读性强

  • 利于前期和后期的扩展以及维护

#include<iostream>
#include<string.h>
using namespace std;

//普通写法
class Calculator
{
public:
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return a + b;
		}
		else if (oper == "-")
		{
			return a - b;
		}
		else if (oper == "*")
		{
			return a * b;
		}
	}
	int a;
	int b;
};

void test01()
{
	Calculator c;
	c.a = 10;
	c.b = 30;
	cout << c.a << "+" << c.b << "=" << c.getResult("+") << endl;
	cout << c.a << "-" << c.b << "=" << c.getResult("-") << endl;
	cout << c.a << "*" << c.b << "=" << c.getResult("*") << endl;
}

//利用多态实现

//实现计算器抽象类
class AbstractCalculator
{
public:
	virtual int getResult()
	{
		return 0;
	}
	int a;
	int b;
};

class AddCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return a + b;
	}
};

class SubCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return a - b;
	}
};

class MulCalculator :public AbstractCalculator
{
public:
	int getResult()
	{
		return a * b;
	}
};

void test02()
{
	//多态使用条件
	//父类指针或者引用指向子类对象
	AbstractCalculator* abc = new AddCalculator();
	abc->a = 100;
	abc->b = 20;
	cout << abc->a << "+" << abc->b << "=" << abc->getResult()<< endl;
	delete abc;

	abc = new SubCalculator();
	abc->a = 100;
	abc->b = 20;
	cout << abc->a << "-" << abc->b << "=" << abc->getResult() << endl;
	delete abc;

	abc = new MulCalculator();
	abc->a = 100;
	abc->b = 20;
	cout << abc->a << "*" << abc->b << "=" << abc->getResult() << endl;
	delete abc;
}

int main()
{
	//test01();
	test02();
//	system("pause");
	return 0;
}

多态好处:

  1. 组织结构清晰

  2. 可读性强

  3. 对于前期和后期扩展以及维护性高

纯虚函数和抽象类

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是是调用子类重写的内容,因此可以将虚函数改为纯虚函数

纯虚函数语法: virtual 返回值类型 函数名 (参数列表) = 0 当类中有了纯虚函数,这个类也称为抽象类。只要有了一个纯虚函数就叫抽象类

抽象类特点:

  • 无法实例化对象

  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

#include<iostream>
using namespace std;

class Base
{
public:
	// 只要有一个纯虚函数,这个类被称为抽象类
	
	virtual void func() = 0; // 纯虚函数
};

class Son :public Base
{
public:
	virtual void func(){
		cout << "func 函数调用" << endl;
	};
};

void test01()
{
	//Base b; // 抽象类无法实例化对象
	//new Base; // 抽象类无法实例化对象
	Base* base = new Son;
	base->func();
	delete base;
}


int main()
{
	test01();
	return 0;
}

多态案例2———制作饮品

#include<iostream>
using namespace std;

class AbstractDrinking
{
public:
	// 煮水
	virtual void Boil() = 0;
	// 冲泡
	virtual void Brew() = 0;

	// 倒入杯中
	virtual void PourInCup() = 0;

	// 加入佐料
	virtual void PutSomething() = 0;

	// 制作饮品
	void makeDrink()
	{
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

class Coffee : public AbstractDrinking
{
public:
	// 煮水
	virtual void Boil() {
		cout << "Coffee 1" << endl;
	}
	// 冲泡
	virtual void Brew() {
		cout << "Coffee 2" << endl;
	}

	// 倒入杯中
	virtual void PourInCup() {
		cout << "Coffee 3" << endl;
	}

	// 加入佐料
	virtual void PutSomething() {
		cout << "Coffee 4" << endl;
	}
};

class Tea : public AbstractDrinking
{
public:
	// 煮水
	virtual void Boil() {
		cout << "Tea 1" << endl;
	}
	// 冲泡
	virtual void Brew() {
		cout << "Tea 2" << endl;
	}

	// 倒入杯中
	virtual void PourInCup() {
		cout << "Tea 3" << endl;
	}

	// 加入佐料
	virtual void PutSomething() {
		cout << "Tea 4" << endl;
	}
};

void dowork(AbstractDrinking* abs)
{
	abs->makeDrink();
	delete abs;
}

void test01()
{
	dowork(new Coffee);
	cout << "--------------------" << endl;
	dowork(new Tea);
}

int main()
{
	test01();
	return 0;
}

虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针释放子类对象

  • 都需要有具体的函数实现

虚析构和纯虚析构区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对像

  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

  3. 拥有纯虚析构函数的类也属于抽象类

#include<iostream>
#include<string.h>
using namespace std;

class Animal
{
public:
	Animal() {
		cout << "Animal create " << endl;
	}
	// 利用虚析构可以解决 父类指针释放子类对象时不干净的问题
	//virtual ~Animal() {
	//	cout << "Animal destory" << endl;
	//}
	// 纯虚析构	需要声明也需要实现
	// 有了纯虚析构,这个类也属于抽象类,无法实例化对象
	virtual ~Animal() = 0;
	virtual void speak() = 0;

};

Animal::~Animal()
{
	cout << "Animal purity virtual destory" << endl;

}

class Cat : public Animal
{
public:
	Cat(string name) :cname(new string(name)){
		cout << "Cat create " << endl;
	}
	virtual void speak() {
		cout << *cname <<"小猫在说话" << endl;
	}
	~Cat() {
		if (cname != NULL) {
			cout << "Cat destory" << endl;
			delete cname;
			cname = NULL;
		}
	}
	string* cname;
};

void test01()
{
	Animal* animal = new Cat("SuYou");
	animal->speak();
	// 父类指针在析构时不会调用子类中析构函数,导致子类中若有堆区数据,出现内存泄露
	delete animal;
}

int main()
{
	test01();
	return 0;
}

多态案例3——电脑组装

#include<iostream>
using namespace std;

// 抽象不同零件类
// 抽象CPU类
class CPU
{
public:
	virtual void calculate() = 0;
};


class VideoCard
{
public:
	virtual void display() = 0;
};

class Memory
{
public:
	virtual void storage() = 0;
};

// 电脑类
class Computer
{
public:
	Computer(CPU * cpu,VideoCard *vc,Memory *mem):m_cpu(cpu),m_vc(vc),m_mem(mem) {}
	void work()
	{
		m_cpu->calculate();
		m_vc->display();
		m_mem->storage();
	}
	~Computer() {
		if (m_cpu != NULL) {
			delete m_cpu;
			m_cpu = NULL;
		}
		if (m_vc != NULL) {
			delete m_cpu;
			m_vc = NULL;
		}
		if (m_mem != NULL) {
			delete m_cpu;
			m_mem = NULL;
		}
	}
private:
	CPU* m_cpu; // CPU的零件指针
	VideoCard* m_vc; // 显卡的零件指针
	Memory* m_mem; // 内存条的零件指针
};

//具体厂商
//Intel厂商
class IntelCPU:public CPU
{
public:
	virtual void calculate() {
		cout << "Intel CPU start calculate" << endl;;
	}
};
class InteIViedoCard :public VideoCard
{
public:
	virtual void display() {
		cout << "Intel VideoCard start dispaly" << endl;;
	}
};
class IntelMemory :public Memory
{
public:
	virtual void storage() {
		cout << "Intel Memory start memory" << endl;;
	}
};

// Lenovo厂商
class LenovoCPU :public CPU
{
public:
	virtual void calculate() {
		cout << "Lenovo CPU start calculate" << endl;;
	}
};
class LenovoViedoCard :public VideoCard
{
public:
	virtual void display() {
		cout << "Lenovo VideoCard start dispaly" << endl;;
	}
};
class LenovoMemory :public Memory
{
public:
	virtual void storage() {
		cout << "Lenovo Memory start memory" << endl;;
	}
};

void test01()
{
	cout << "第一台电脑--------------" << endl;
	// 第一台电脑零件
	CPU* intelCpu = new IntelCPU;
	VideoCard* intelCard = new InteIViedoCard;
	Memory* intelMem = new IntelMemory;

	// 创建第一台电脑	
	Computer* computer1 = new Computer(intelCpu, intelCard, intelMem);
	computer1->work();
	delete computer1;

	cout << "第二台电脑--------------" << endl;
	// 创建第二台电脑	
	Computer* computer2 = new Computer(new LenovoCPU,new LenovoViedoCard,new LenovoMemory);
	computer2->work();
	delete computer2;

	cout << "第三台电脑--------------" << endl;
	// 创建第三台电脑	
	Computer* computer3 = new Computer(new LenovoCPU, new InteIViedoCard, new LenovoMemory);
	computer3->work();
	delete computer3;
}

int main()
{
	test01();
	return 0;
}

文件操作

程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放 通过文件可以将数据持久化 C++中对文件操作需要包含头文件<fstream>

文件类型分为两种:

  1. 文本文件——文件以文本的ASCII码形式存储在计算机中

  2. 二进制文件——文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

操作文件的三大类:

  • ofstream:写操作

  • ifstream:读操作

  • fstream:读写操作

文本文件

写文件

写文件步骤如下:

  1. 包含头文件 #include <fstream>

  2. 创建流对象 ofstream ofs;

  3. 打开文件 ofs.open("文件路径",打开方式);

  4. 写数据

    ofs<<"写入的数据";

  5. 关闭文件

    ofs.close();

文件打开方式:

注意:文件打开方式可以配合使用,利用|操作符 例如:用二进制方式写文件 ios::binary | ios:: out

#include<iostream>
using namespace std;
#include<fstream>

void test01() {
	ofstream ofs;
	ofs.open("W:\\代码\\c++学习\\黑马\\文件操作学习\\test.txt", ios::out);
	ofs << "姓名:张三" << endl;
	ofs << "性别:男" << endl;
	ofs << "年龄:18" << endl;
	ofs.close();
}
int main()
{
	test01();
	return 0;
}

总结:

  • 文件操作必须包含头文件fstream

  • 读文件可以利用ofstream,或者fstream类

  • 打开文件时候需要指定操作文件的路径,以及打开方式

  • 利用<<可以向文件中写数据

  • 操作完毕,要关闭文件

读文件

读文件与写文件步骤相似,但是读取方式相对于比较多

读文件步骤如下:

  1. 包含头文件

    #include <fstream>

  2. 创建流对象

    ifstream ifs;

  3. 打开文件并判断文件是否打开成功

    ifs.open("文件路径",打开方式);

  4. 读数据

    四种方式读取

  5. 关闭文件

    ifs.close();

#include<iostream>
using namespace std;
#include<fstream>
#include<string>

void test01() {
	ifstream ifs;
	ifs.open("W:\\代码\\c++学习\\黑马\\文件操作学习\\test.txt", ios::in);
	if (!ifs.is_open())
	{
		cout << "打开文件失败"<< endl;
		return;
	}
	//第一种
	//char buf[1024] = { 0 };
	//while (ifs >> buf) {
	//	cout << buf << endl;
	//}
	//ifs.close();
	
	//第二种
	//char buf[1024] = { 0 };
	//while (ifs.getline(buf,sizeof(buf))) {
	//	cout << buf << endl;
	//}
	
	//第三种
	//string buf;
	//while ( getline(ifs,buf)) {
	//	cout << buf << endl;
	//}

	//第四种,不常用
	//char c;
	//while ((c = ifs.get()) != EOF)
	//{
	//	cout << c;
	//}
	ifs.close();
}
int main()
{
	test01();
	return 0;
}

总结:

  • 读文件可以利用ifstream,或者fstream类

  • 利用is_open函数可以判断文件是否打开成功close关闭文件

二进制文件

以二进制的方式对文件进行读写操作 打开方式要指定为ios::binary

写文件

二进制方式写文件主要利用流对象调用成员函数write 函数原型: ostream& write(const char * buffer,int len); 参数解释: 字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include<iostream>
using namespace std;
#include<fstream>
class Person
{
public:
	char m_Name[64];
	int m_Age;
};

void test01() {
	ofstream ofs;
	ofs.open("W:\\代码\\c++学习\\黑马\\文件操作学习\\Person.txt", ios::binary | ios::out);
	Person p = { "张三",18 };
	ofs.write((const char*)&p, sizeof(Person));
	ofs.close();
}
int main()
{
  test01();
	return 0;
}

总结: 文件输出流对象可以通过write函数,以二进制方式写数据

读文件

二进制方式读文件主要利用流对象调用成员函数read 函数原型: istream&read(char *buffer,int len); 参数解释: 字符指针buffer指向内存中一段存储空间。len是读写的字节数

#include<iostream>
using namespace std;
#include<fstream>
class Person
{
public:
	char m_Name[64];
	int m_Age;
};
void test01() {
	ifstream ifs;
	ifs.open("W:\\代码\\c++学习\\黑马\\文件操作学习\\Person.txt", ios::binary | ios::in);
	if (!ifs.is_open())
	{
		cout << "打开文件失败"<< endl;
		return;
	}
	Person p;
	ifs.read((char*)&p, sizeof(Person));
	cout << p.m_Name << p.m_Age << endl;
	ifs.close();
}
int main()
{
	test01();
	return 0;
}

模板

C++提供两种模板机制:函数模板和类模板

模板的特点:

  • 模板不可以直接使用,它只是一个框架

  • 模板的通用并不是万能的

函数模板

函数模板作用:

  • 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表。

语法:

template<typename T>
函数声明或定义

解释:

  • template--声明创建模板

  • typename--.表面其后面的符号是一种数据类型,可以用class代替

  • T--通用的数据类型,名称可以替换,通常为大写字母

#include<iostream>
using namespace std;

void swapint(int &a,int &b)
{
	int temp = a;
	a = b;
	b = temp;
}
void swapdouble(double& a, double& b)
{
	double temp = a;
	a = b;
	b = temp;
}
template<typename T>
void myswap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}
void test01() {
	int a = 10, b = 20;
	//swapint(a, b);
	//cout << a << b << endl;
	double c = 1.11, d = 2.22;
	//swapdouble(c, d);
	//cout << c << d << endl;
	//两种方法使用函数模板
	
	//1.自动类型推导
	//myswap(a, b);
	//cout << a << b << endl;
	//2.显示指定类型
	myswap<int>(a, b);
	cout << a << b << endl;
}
int main()
{
	test01();
	return 0;
}

总结:

  • 函数模板利用关键字template

  • 使用函数模板有两种方式:自动类型推导、显示指定类型

  • 模板的目的是为了提高复用性,将类型参数化

注意事项

  • 自动类型推导,必须推导出一致的数据类型T,才可以使用

  • 模板必须要确定出T的数据类型,才可以使用

案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序

  • 排序规则从大到小,排序算法为选择排序

  • 分别利用char数组和int数组进行测试

#include<iostream>
using namespace std;

//实现通用对数组进行排序的函数
//规则从大到小
//算法选择
//测试 char数组、int数组

template<typename T>
void myswap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}

template<typename T>
void mysort(T arr[],int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i;//认定最大值下标
		for (int j = i + 1; j < len; j++)
		{
			if (arr[max] < arr[j])
			{
				max = j;//更新最大值下标
			}
		}
		if (max != i)
		{
			myswap(arr[max], arr[i]);
		}
	}

}
template<typename T>
void printArry(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		cout << arr[i];
	}
	cout << endl;
}

void test01() 
{
	char charArr[] = "badcfe";
	int len = sizeof(charArr) / sizeof(char);
	mysort(charArr, len);
	printArry(charArr, len);
	int intArr[] = { 7,5,1,3,9,7,2,5,6 };
	len = sizeof(intArr) / sizeof(int);
	mysort(intArr, len);
	printArry(intArr, len);
}
int main()
{
	test01();
	return 0;
}

普通函数与函数模板的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)

  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型接换

  • 如果利用显示指定类型的方式,可以发生隐式类型转换

#include<iostream>
using namespace std;

int myadd01(int a, int b)
{
	return a + b;
}
template<typename T>
T myadd02(T a, T b)
{
	return a + b;
}

void test01() {
	int a = 10;
	int b = 20;
	char c = 'c';
	cout << myadd01(a, c)<<endl;
	cout << myadd02(a, b) << endl;
	//自动类型推导时不会发生隐式类型转化
	//cout << myadd02(a, c) << endl;
	//显示指定类型会发生隐式类型转化
	cout << myadd02<int>(a, c) << endl;
}
int main()
{
	test01();
	return 0;
}

总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

普通函数与函数模板的调用规则

调用规则如下:

  1. 如果函数模板和普通函数都可以实现(即重名),优先调用普通函数

  2. 可以通过空模板参数列表来强制调用函数模板

  3. 函数模板也可以发生重载

  4. 如果函数模板可以产生更好的匹配,优先调用函数模板

总结:既然提供了函数模板,最好就不要提供普通函数,否则容易出现二义性

模板的局限性

局限性: 模板的通用性并不是万能的

#include<iostream>
using namespace std;
#include<string.h>
class Person
{
public:
	string name;
	int age;
	Person(string name,int age):name(name),age(age){}
};

template<class T>
bool mycompare(T&a,T&b)
{
	if (a == b)
	{
		return true;
	}
	else {
		return false;
	}
}

template<> bool mycompare(Person& a, Person& b)
{
	if (a.name == b.name && a.age==b.age)
	{
		return true;
	}
	else {
		return false;
	}
}
void test01() {
	int a = 10;
	int b = 10;
	bool ret = mycompare(a, b);
	if (ret)
	{
		cout << "a==b"<<endl;
	}
	else {
		cout << "a!=b"<<endl;
	}
}
void test02()
{
	Person p1("Tom", 18);
	Person p2("Tom", 18);
	bool ret = mycompare(p1, p2);
	if (ret)
	{
		cout << "p1 == p2" << endl;
	}
	else {
		cout << "p1!=p2" << endl;
	}
}
int main()
{
	test02();
	return 0;
}

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化

  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的楼模板

类模板

语法

类模板作用: 建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表。

#include<iostream>
using namespace std;
#include<string.h>
template<class NameType,class AgeType>
class Person
{
public:
	NameType name;
	AgeType age;
	Person(NameType name, AgeType age):name(name),age(age){}
	void showperson() {
		cout << this->name << endl;
		cout << this->age << endl;
	}
};


void test01() {
	Person<string, int> p1("SuYou", 22);
	p1.showperson();
}
int main()
{
	test01();
	return 0;
}

总结: 类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

类模板与函数模板区别

类模板与函数模板区别主要有两点:

  • 类模板没有自动类型推导的使用方式

  • 类模板在模板参数列表中可以有默认参数

#include<iostream>
using namespace std;
#include<string.h>

template<class NameType,class AgeType=int>
class Person
{
public:
	NameType name;
	AgeType age;
	Person(NameType name, AgeType age):name(name),age(age){}
	void showperson() {
		cout << "name:" << this->name << endl;
		cout << "age:" << this->age << endl;
	}
};



void test01() {
	//类模板没有自动类型推导
	//Person p1("SuYou", 22);
	Person<string,int>p1("SuYou", 22);
	p1.showperson();
}

void test02() {
	//类模板在模板参数列表中可以有默认参数
	Person<string>p1("Hello", 80);
	p1.showperson();

}
int main()
{
	test02();
	return 0;
}

类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建

  • 类模板中的成员函数在调用时才创建

类模板对象做函数参数

学习目标: 类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

  • 指定传入的类型——直接显示对象的数据类型

  • 参数模板化——将对象中的参数变为模板进行传递

  • 整个类模板化——将这个对象类型模板化进行传递

#include<iostream>
using namespace std;
#include<string.h>
template<class T1, class T2>
class Person
{
public:
	T1 name;
	T2 age;
	Person(T1 name,T2 age):name(name),age(age){}
	void show() {
		cout << "name: " << this->name <<endl <<  "age: " << this->age << endl;
	}
};
//1.指定传入的类型
void printperson1(Person<string, int>& p)
{
	p.show();
}
void test01() {
	Person<string, int>p("Suyou", 22);
	printperson1(p);
}
//2.参数模板化
template<class T1,class T2>
void printperson2(Person<T1, T2>& p)
{
	p.show();
	cout << "T1 type: " << typeid(T1).name() << endl;
	cout << "T2 type: " << typeid(T2).name() << endl;
}
void test02() {
	Person<string, int>p("Hello", 90);
	printperson2(p);
}
//3.整个类都模板滑
template<class T>
void printperson3(T& p)
{
	p.show();
	cout << "T type: " << typeid(T).name() << endl;
}
void test03() {
	Person<string, int>p("World", 99999);
	printperson3(p);
}

int main()
{
	//test01();
	//test02();
	test03();
	return 0;
}

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参

  • 使用比较广泛是第一种:指定传入的类型

类模板与继承

当类模板碰到继承时,需要注意一下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

  • 如果不指定,编译器无法给子类分配内存

  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

#include<iostream>
using namespace std;

template<class T>
class Base
{
public:
	T m;

};

//class Son :public Base //错误,必须要知道父类中T的类型才能继承给子类
class Son:public Base<int>
{

};
void test01() {
	Son s1;
}

//若想灵活指定父类中T的类型,子类也需要变成类模板
template<class T1,class T2>
class Son2 :public Base<T2>
{
public:
	T1 obj;
	Son2() 
	{
		cout << "T1 type: " << typeid(T1).name() << endl;
		cout << "T2 type: " << typeid(T2).name() << endl;
	}
};
void test02()
{
	Son2<int, char>S2;
}

int main()
{
	test02();
	return 0;
}

总结: 如果父类是类模板,子类需要指定出父类中T的数据类型

类模板成员函数类外实现

#include<iostream>
using namespace std;
#include<string.h>

template<class T1,class T2>
class Person
{
public:
	//Person(T1 name,T2 age):name(name),age(age){}
	Person(T1 name, T2 age);
	T1 name;
	T2 age;
	void show();
};
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
	this->name = name;
	this->age = age;
}
template<class T1, class T2>
void Person<T1, T2>::show()
{
	cout << name << age << endl;
}


void test01() {
	Person<string, int>p("SuYou", 22);
	p.show();
}
int main()
{
	test01();
	return 0;
}

总结: 类模板中成员函数类外实现时,需要加上模板参数列表

类模板分文件编写

学习目标: 掌握类模板成员函数分文件编写产生的问题以及解决方式

问题: 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

  • 解决方式1:直接包含.cpp源文件

  • 解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

类模板与友元

学习目标: 掌握类模板配合友元函数的类内和类外实现

  • 全局函数类内实现-直接在类内声明友元即可

  • 全局函数类外实现-需要提前让编译器知道全局函数的存在

#include<iostream>
using namespace std;
#include<string.h>
//通过全局函数打印Person信息

//提前让编译器知道Person类存在
template<class T1, class T2>
class Person;

//全局函数在类外实现
template<class T1, class T2>
void print2(Person<T1, T2> p)
{
	cout << "类外实现——name:" << p.name << endl << "类外实现——age:" << p.age << endl;
}


template<class T1, class T2>
class Person
{
public:
	Person(T1 name,T2 age):name(name),age(age){}

private:
	T1 name;
	T2 age;
	//全局函数类内实现
	friend void print(Person<T1, T2> p)
	{
		cout << "name:" << p.name << endl << "age:" << p.age << endl;
	}
	//全局函数类外实现 加空模板参数列表 如果是全局函数是类外实现,需要让编译器提前知道这个函数存在
	friend void print2<>(Person<T1, T2> p);
};


void test01() {
	Person<string, int>p("SuYou", 22);
	print2(p);
}
int main()
{
	test01();
	return 0;
}

总结: 建议全局函数做类内实现,用法简单,而且编译器可以直接识别

案例

案例描述:实现一个通用的数组类,要求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储

  • 将数组中的数据存储到堆区

  • 构造函数中可以传入数组的容量

  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题

  • 提供尾插法和尾删法对数组中的数据进行增加和删除

  • 可以通过下标的方式访问数组中的元素

  • 可以获取数组中当前元素个数和数组的容量

#include<iostream>
using namespace std;
#include<string.h>



template<class T>
class MyArray
{private:
	T* pAddress; // 指针指向堆区开辟的真实数组
	int m_Capacity; // 数组容量
	int m_Size; // 数组大小
public:
	MyArray(int capacity):m_Capacity(capacity),m_Size(0),pAddress(new T[capacity]){
		//cout << "MyArray 有参构造调用" << endl;
	}
	//MyArray(int capacity){
	//	cout << "MyArray 有参构造调用" << endl;
	//	this->m_Capacity = capacity;
	//	this->m_Size = 0;
	//	this->pAddress = new T[capacity];
	//}
	~MyArray()
	{
		//cout << "MyArray 析构调用" << endl;
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;
		}
	}
	MyArray(const MyArray& arr)
	{
		//cout << "MyArray 拷贝构造调用" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		//this->pAddress = arr.pAddress;浅拷贝的问题

		//深拷贝
		this->pAddress = new T[arr.m_Capacity];
		//将arr中的数据拷贝过来
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
	}
	//operator=防止浅拷贝问题
	MyArray& operator=(const MyArray& arr)
	{
		//cout << "MyArray orperator=调用" << endl;
		//先判断原来堆区是否有数据,如果you先释放
		if (this->pAddress != NULL)
		{
			delete[] this->pAddress;
			this->pAddress = NULL;	
			this->m_Capacity = 0;
			this->m_Size = 0;
		}
		//深拷贝
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;
	}
	
	//尾插法
	void Push_Back(const T& val)
	{
		//判断是否满了
		if (this->m_Capacity == this->m_Size)
		{
			return;
		}
		this->pAddress[this->m_Size] = val; //在数组末尾插入数据
		this->m_Size++; // 更新数组大小
	}

	//尾删法
	void Pop_Back()
	{
		// 逻辑尾删,让用户访问不到最后一个元素
		// 判断是否已经空了
		if (this->m_Size == 0)
		{
			return;
		}
		this->m_Size--;
	}

	// 通过下标方式访问到数组中的元素
	T& operator[](int index)
	{
		return this->pAddress[index];
	}

	//返回数组容量
	int getCapacity()
	{
		return this->m_Capacity;
	}
	// 返回数组大小
	int getSize()
	{
		return this->m_Size;
	}
};

void printintArray(MyArray<int> & arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << arr[i] << endl;
	}
}

void test01() {
	MyArray<int>arr1(5);
	//MyArray<int>arr2(arr1);
	//MyArray<int>arr3(100);
	//arr3 = arr1;
	for (int i = 0; i < 5; i++)
	{
		arr1.Push_Back(i);
	}
	cout << "arr1的打印输出为:" << endl;
	printintArray(arr1);
	cout << "arr1的容量为:" << arr1.getCapacity() << endl;
	cout << "arr1的大小为:" << arr1.getSize() << endl;
	MyArray<int>arr2(arr1);
	cout << "arr2的打印输出为:" << endl;
	printintArray(arr2);
	arr2.Pop_Back();
	cout << "arr2尾删后:" << endl;
	cout << "arr2的容量为:" << arr2.getCapacity() << endl;
	cout << "arr2的大小为:" << arr2.getSize() << endl;



}

//测试自定义数据类型
class Person
{
public:
	string name;
	int age;
	Person() {}
	Person(string name, int age) :name(name), age(age) {}
};

void printpersonArray(MyArray<Person>& arr)
{
	for (int i = 0; i < arr.getSize(); i++)
	{
		cout << "name:" << arr[i].name << "  age:" << arr[i].age << endl;
	}
}

void test02()
{
	MyArray<Person> arr(10);
	Person p1("孙悟空", 999);
	Person p2("韩信", 22);
	Person p3("东方耀", 19);
	Person p4("妲己", 6666);
	Person p5("猪八戒", 58);

	arr.Push_Back(p1);
	arr.Push_Back(p2);
	arr.Push_Back(p3);
	arr.Push_Back(p4);
	arr.Push_Back(p5);

	printpersonArray(arr);
	cout << "arr的容量为:" << arr.getCapacity() << endl;
	cout << "arr的大小为:" << arr.getSize() << endl;

}

int main()
{
	//test01();
	test02();
	return 0;
}

STL初识

STL基本概念

  • STL(Standard Template Library,标准模板库)

  • STL从广义上分为: 容器(container) 算法(algorithm) 选代器(iterator)

  • 容器和算法之间通过迭代器进行无缝连接

  • STL几乎所有的代码都采用了模板类或者模板函数

STL六大组件

STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器

  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来有放数据

  2. 算法:各种常用的算法,如sort、find、copy、for_each等

  3. 迭代器:扮演了容器与算法之间的胶合剂。

  4. 仿函数:行为类似函数,可作为算法的某种策略。

  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西a。

  6. 空间配置器:负责空间的配置与管理。

容器分为序列式容器和关联式容器两种:

  • 序列式容器: 强调值的排序,序列式容器中的每个元素均有固定的位置

  • 关联式容器: 二叉树结构,各元素之间没有严格的物理上的顺序关系

算法:问题之解法也 有限的步骤,解决逻辑或数学上的问题,这一门学科我们叫做算法(Algorithms)

算法分为:质变算法和非质变算法。

  • 质变算法:是指运算过程中会更改区间内的元素的内容。例如拷贝,替换,删除等等

  • 非质变算法:是指运算过程中不会更改区间内的元素内容,例如查找、计数、遍历、寻找极值等等

迭代器:容器和算法之间粘合剂

  • 提供一种方法,使之能够依序寻访某个容器所含的各个元素,而而又无需暴露该容器的内部表示方式。

  • 每个容器都有自己专属的迭代器

  • 迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为为指针

迭代器种类:

常用的容器中迭代器种类为双向迭代器,和随机访问迭代器

容器算法迭代器初识

vector存放内置数据类型

  • 容器: vector

  • 算法: for_each

  • 迭代器: vector<int>::iterator

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇