前言
之前在黑马程序员的零基础课程里系统学习了c++,但是黑马程序员的课程还是太慢了,近四百节课让人很难受 ,基本上前一百节后就已经对C语言了解的差不多了(和c++差不多),两百节之后对c++也基本熟悉了。
总归还是太慢了,其中课程的很多内容都是重复出现的,为了方便我将课程中可以省略的重复步骤省略,将其余内容直接以代码加注释的方式展示出来,在其中还加入了《c++程序设计》机械工业出版社的黑皮书的内容进去作为补充。
c++是非常基础的语言,学习也比较容易,如果减去自己的代码编写时间单纯的看懂就可以的话其实不用一个星期就能基本了解。
但是还是需要多练习,很多程序bug等(有版本不适问题,操作系统不适问题)都在是实操中发现并处理。
简单的学习之后就可以应对csp考试,数据结构等课程了就。
我就是本来用java,考csp前学习回顾一下c++想用c++考。java岗的就业情况也比较饱和,会c++一定是很合算的事。
附上第六部分链接
本期主要内容
模版模版还是模版
类模版,函数模版,还有一个模版实例,模版学习是为了之后的stl使用,可以说stl就是成熟的模版,使用成熟的模版就像调库一样,能够大量减少编程时间和难度。学习模版就是为了理解stl的原理。
下面代码可以直接运行,在vs或者dev环境运行,类对象声明都在前面,main中只有测试函数和注释。
代码
xuexic++7.cpp
// 注释 有字的情况下 当前行注释ctrl + k + c
// Ctrl + b 编译
// Ctrl + l删除当前一行
// Ctrl alt l 显示右边的那个资源栏
#include <iostream>
#include<string>
#include"dividehouse.hpp"//分文件编写
#include"MyArray.hpp"
using namespace std;
//函数模版
void swapint2number(int &a,int &b) {
int temp = a;
a = b;
b = temp;
}
void swapDouble2number(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
void test01() {
double num1 = 1.0;
double num2 = 2.0;
swapDouble2number(num1, num2);
int numi1 = 1;
int numi2 = 2;
swapint2number(numi1, numi2);
//除了类型不一样别的都一样,交换逻辑确定,而形参类别不要确定,就可以做模版
}
template<typename Test>//声明一个模版,等会别报错,Test是一个通用类型
void mySwap(Test& a, Test& b) {
Test temp = a;
a = b;
b = temp;
}
void test02() {
int numa = 10;
int numb = 20;
//两种使用模版的方式
//机器自动类型推导
mySwap(numa, numb);//调换
//手动指示
mySwap<int>(numa, numb);//还原
cout << numa << "\t" <<numb<< endl;
}
template <class 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) {
char temp = arr[max];
arr[max] = arr[i];
arr[i] = temp;
}
}
}
template <class t>//后面的用后面也得写
//打印数组模版
void printarr(t arr[],int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << endl;
}
}
void Test03() {
char chararr[] = "edcba";
int lentharr = sizeof(chararr) / sizeof(char);
mySort(chararr, lentharr);
printarr(chararr, lentharr);
}
//普通函数
int addnumber(int a,int b) {
return a + b;
}
//函数模版
template<class N>
N myAdd(N a,N b) {
return a + b;
}
void test04() {
int a = 10;
int b = 20;
cout << addnumber(a, b) << endl;
char c = 'c';
char d = 'b';
cout << addnumber(c, d) << endl;
//发生隐式类型转换
//输出的其实是ascill码
//cout << myAdd(a, d);//不同种类的不会隐式转换
cout << myAdd<int>(a, c);
}
void myprintarr(int arr[], int len) {
cout << "调用了那普通打印函数" << endl;
}
template<class M>
void myprintarr(M arr, int len) {
cout << "调用了模版打印函数" << endl;
}
void test05() {
int array[3] = { 0,1,2 };
myprintarr(array, 3);//会选择普通函数*优先调用普通函数,必须有声明和实现
myprintarr<>(array,3);//强制调用函数模版(空模版参数)
//参数更符合,比如参数数量不一样,和普通函数重载一样
//函数模版也可以重载,会选择更匹配的重载函数,参数数量对的
//char类型(使用函数模版不用发生隐式转换,所以优先选择函数模版)
//一般实际开发中有了模版就不会搞同名普通函数,避免二义性
}
class Person {
public:
Person(string na,int r) {
name = na;
age = r;
}
string name;
int age;
};
template<class U>
void myCompare(U a,U b) {
if (a == b) {
cout << "相等" << endl;
}
else {
cout << "不相等" << endl;
}
}
//函数模版
template<> void myCompare(Person a, Person b) {
if (a.name == b.name&&a.age==b.age) {
cout << "相等" << endl;
}
else {
cout << "不相等" << endl;
}
}
void test06() {
int a = 10;
int b = 20;
myCompare(a, b);
Person p1("xiaoma", 10);
Person p2("xiaoma", 10);
// myCompare(p1, p2);
//不能对比,因为有不同的数据类型
//重载运算符或者
//专门搞一个Person的版本的模版(会优先走这个)
myCompare(p1, p2);
//但是感觉用处不大,和重载没啥区别,反正也能对应一个person类的处理
//学习模版是为了使用stl的模版,而不是写模版,写的容易有漏洞
}
//类模版
template<class NameType, class AgeType>
class mouse {
public:
mouse(NameType name, AgeType length) {
this->m_name = name;
this->m_length = length;
//构造函数
}
string m_name;
int m_length;
void showdata() {
cout << "名子是:" << this->m_name <<"\t" << "长度是:" << this->m_length << endl;
}
};
void test07() {
mouse<string, int>p1("jerry", 22);//<>里面是模版参数列表
p1.showdata();
}
template<class NameType, class AgeType=int>//类模版中可以有默认参数
class dog {
public:
dog(NameType name, AgeType weight) {
this->m_weight = weight;
this->n_name = name;
}
int m_weight;
string n_name;
void showDate() {
cout << this->m_weight << "千克重的傻狗:" << this->n_name << endl;
}
};
void test08() {
// dog d1(100,"汪")//错误,不能使用自动类型推导
dog<string,int>d1("me",100);//只能使用显示指定类型的方式
d1.showDate();
}
void test09() {
dog<string>d2("xiaoma",1000);//类模版重可以有默认参数
d2.showDate();//可以省略输入那个int,传了就会用自己传的,没有传才会使用默认的。
}
class cat {
public:
void showCat() {
cout << "cat is soft" << endl;
}
};
template<class T>
class Myclass {
public:
T obj;
//类模版中的成员函数
//不调用就不会创建,也就不会报错
void func1() {
dog<string>d2("xiaoma", 1000);
obj.showDate();
}//第一个可以直接运行
void func2() {
obj.showCat();
}
};
void test10() {
Myclass<cat>m;//用了cat就只能调用第二个,用了dog就只能调用第一个
// m.func1();
m.func2();//只有调用的时候类模版才会创建函数,确定好创建哪个之后才会创建
}
//类模版对象做函数参数
template<class T1,class T2>
class monkey {
public:
T1 m_name;
T2 m_age;
monkey(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
void showmonkey() {
cout << "this monkey's name is " << this->m_name << ",and his age is " << this->m_age << endl;
}
};
//1.指定传入类型
void printmonkey1(monkey<string, int>& p) {//指定传入类型
p.showmonkey();
}
//2.将参数模版化
template<class T1,class T2>
void printmonkey2(monkey<T1, T2>&p) {
p.showmonkey();
cout << "t1的类型是" << typeid(T1).name() << endl;//string的原名很长,是库里面的数据类型
cout << "t2的类型是" << typeid(T2).name() << endl;//查看类型的代码
}
//3.整个类模版化
template <class T>
void printmonkey3(T& p) {
p.showmonkey();
cout << "t的类型是" << typeid(T).name() << endl;//string的原名很长,是库里面的数据类型
//类型是 class类(monkey类)
}
void test11() {
monkey<string, int>p("牛马", 66);
printmonkey1(p);
}
void test12() {
monkey<string, int>p("sunny", 18);
printmonkey2(p);
}
void test13() {
monkey<string, int>p("姐姐", 18);
printmonkey3(p);
}
//类模版与继承
template<class T>
class Base {
T m;
};
class Son1 :public Base<int>{//必须知道父类的数据类型
public:
};
//想灵活一点,子类也变成类模版
template<class T1,class T2>
class Son2 :public Base<T2> {//必须知道父类的数据类型
public://T2交给父类,父类是string
Son2() {
cout << "Son2的构造函数" << endl;
cout << "t1的类型是" << typeid(T1).name() << endl;
cout << "t2的类型是" << typeid(T2).name() << endl;
}
};
void test14() {
Son1 s1;
Son2<int,string>s2;//int和string指向的是T1,和T2
}
//类模版中成员函数的类外实现
template<class T1,class T2>
class house1{
public:
house1(T1 name, T2 age); /* {
this->m_name = name;
this->m_age = age;
}*/
//类外实现
T1 m_name;
T2 m_age;
void showhouse1();/* {
cout << "name:" << this->m_name << "age:" << this->m_age << endl;
}*/
};
//构造函数的类外实现
template<class T1,class T2>
house1<T1,T2>::house1(T1 name, T2 age) {//类模版的类外实现要多一个步骤就是在后面加T1,T2
this->m_age = age;
this->m_name = name;
}
//成员函数的类外实现
template<class T1, class T2>
void house1<T1,T2>::showhouse1() {//一样的需求,类模版中的成员函数必须有
cout << "name:" << this->m_name << "age:" << this->m_age << endl;
}//类外实现必须让编译器知道这个是类模版
void test15() {
house1<string, int >p("banbi", 19);
p.showhouse1();
}
//类模版分文件编写
void test16() {
dividehouse<string, int>p("burst", 77);
p.showdivide();
}
/*
//全局函数类外实现
//要把他也写成函数模版类型才能型号匹配
template<class T1, class T2>
class friendpeople;
template<class T1, class T2>
void printfriend2(friendpeople<T1, T2> p)
{
cout << "2友名:" << p.m_name << "2age:" << p.m_age << endl;
}
*/
//类模版配合友元函数类内类外实现
template<class T1, class T2>
class friendpeople {
//这种b方法太傻比了,还全都报错,这么写代码队友得打死你,就这样吧,注释掉了
//friend void printfriend2(friendpeople<T1, T2>p);
//要把他也写成函数模版类型才能型号匹配
//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在。
//可以把函数放到这个上面
//通过全局函数来打印,类内实现(加了friend就是全局函数了)
friend void printfriend(friendpeople<T1, T2>p)
{
cout << "友名:" << p.m_name << "age:" << p.m_age << endl;
}
public:
friendpeople(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
void test17() {
friendpeople<string, int>p1("sunny", 113);
printfriend(p1);
}
/*void test18() {
friendpeople<string, int>p1("xiaoma", 88);
printfriend2(p1);
}*/
void test18() {
MyArray<int>arr1(5);
//MyArray<int>arr2(arr1);//拷贝构造函数
//MyArray<int>arr3(100);//有参构造
//arr3 = arr1;//赋值深拷贝
for (int i = 0; i < 6; i++) {
arr1.Push_back_Tail(i);
//尾插法插入0~5
}
cout << "arr1的打印输出是:" << endl;
arr1.printIntArray(arr1);//打印
cout << "arr1的容量是多少"<<arr1.getcapacity()<<endl;
cout << "arr1的长度是多少"<<arr1.getSize()<<endl;
cout << "删除尾部后" << endl;
arr1.delete_back_Tail();
arr1.printIntArray(arr1);//打印
cout << "arr1的容量是多少" << arr1.getcapacity() << endl;
cout << "arr1的长度是多少" << arr1.getSize() << endl;
}
class TESTmy {
public:
TESTmy() {
};
TESTmy(string name,int age) {
this->m_name = name;
this->m_age = age;
}
string m_name;
int m_age;
};
void printPersonArray(MyArray<TESTmy>& arr) {
for (int i = 0; i < (arr.getSize()); i++) {
cout << arr[i].m_name << endl;
cout << arr[i].m_age << endl;
}
}
void test19() {
MyArray<TESTmy>arr1(7);
TESTmy p1("xiaoma", 22);
TESTmy p2("sunny", 32);
TESTmy p3("maomaomao", 18);
TESTmy p4("baby", 15);
TESTmy p5("fs", 666);
arr1.Push_back_Tail(p1);
arr1.Push_back_Tail(p2);
arr1.Push_back_Tail(p3);
arr1.Push_back_Tail(p4);
arr1.Push_back_Tail(p5);
printPersonArray(arr1);
cout << "arr1的容量是多少" << arr1.getcapacity() << endl;
cout << "arr1的长度是多少" << arr1.getSize() << endl;
cout << "删除尾部后" << endl;
arr1.delete_back_Tail();
printPersonArray(arr1);
cout << "arr1的容量是多少" << arr1.getcapacity() << endl;
cout << "arr1的长度是多少" << arr1.getSize() << endl;
}
int main()
{
std::cout << "Hello World!\n";
//模版。提高复用性,泛型编程思想
//通用性强,不能直接使用,
//函数模版和类模版
//函数模版
//返回值和参数类型之类的一开始不声明,使用的时候声明,用一个虚拟的类型来代表
//什么时候会用到模版
test02();
//为什么会用到模版 ?
/*
* 类型参数化!
*/
//提高代码的复用性,将函数类型参数化
//模版确定数据类型了才可以使用
//自动推导数据类型的时候,模版的最后声明参数类型必须是一样的,不能一个用int,一个用Double
//案例,通用数组选择排序函数,升序
//cahr and int
Test03();
//使用其他数据类型也是一样的,核心思想就是类型变参数
//普通函数调用的时候可以发生自动类型转换(隐式类型转换)
//函数模版用自动类型推导的时候不可以发生隐式类型转换
//使用指定类型,可以发生隐藏式类型转化
test04();
//普通函数和函数模版的调用规则
//可以重载,会优先调用普通函数,可以用空模版参数列表来强制调用模版函数
//函数模版也可以发生函数重载
//当函数模版可以更好的匹配参数的时候,优先调用模版函数
test05();
//模版的局限性
//自定义数据类型的模版(普通模版无法使用,需要做具体化特殊实现)
test06();
//类模版
//创建一个通用类,类中的成员数据类型可以不具体制定 用一个虚拟的类型来代表
test07();
//类模版和函数模版声明相似
//类模版和函数模版的区别
//类模版没有自动类型推导的使用方式
//类模版在模版参数列表中可以有默认参数
test08();
test09();
//类模版中成员函数的创建时机
// 类模版中成员函数在调用时才去创建
test10();
//类模版对象做函数参数
//向函数传参的方式
/*指定传入的类型
参数模版化
整个类模版化*/
test11();
//参数模版化
test12();
//整个类模版化
test13();
//最常用的是指定传入类型,比较直接容易理解,方便别人理解
//好的代码要求简单易懂
//类模版与继承
/*
* 子类继承的是父类是一个类模版的话,子类在声明的时候要指出父类的类型
* 如果不能指定,编译器不会给子类分配内存
* 如果想灵活一点,子类也可以变成类模版
*/
test14();
//类模版成员函数的类外实现
test15();
//类模版分文件编写
/* 类模版中成员函数创建时间是调用阶段,导致分文件编写的时候链接不到
解决方案
1 直接包含.cpp
2 将声明和实现写到同一个文件里面,改后缀名为.hpp,hpp是约定的名字,不是强制的名字 */
test16();
//第一种方法在这里说明直接,把.h改成.cpp。
//编译器直接去看.cpp,这个时候会从cpp里面跳转到.h.
//但是实际开发中不会让别人直接看源码,所以一般用第二种
//一般使用第二种
//类模版和友元
//类模版配合友元函数类内类外实现
test17();
//类模版案例
//要求,
//数组中的数据存到堆区
//构造函数中可以指定传入数组的容量
// 尾插法,尾删除法
//自己提供深拷贝来解决浅拷贝问题...
test18();
test19();
system("pause");
}
dividehouse.hpp
#include <iostream>
#include<string>
using namespace std;
template<class T1, class T2>
class dividehouse {
public:
T1 m_name;
T2 m_age;
dividehouse(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
void showdivide() {
cout << "this monkey's name is " << this->m_name << ",and his age is " << this->m_age << endl;
}
};
MyArray.hpp
//自己的通用类数组
#pragma once//防止头文件重复包含
#include <iostream>
#include<string>
using namespace std;
template<class T>
class MyArray {
public:
//有参构造函数
MyArray(int capa) {
this->m_capacity = capa;
this->m_Size = 0;
this->pAddress = new T[this->m_capacity];
}
//拷贝构造函数
MyArray(const MyArray&arr) {
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];
//一一赋值
}
}
//重载=来防止浅拷贝
//返回自身为了能够链式操作
MyArray& operator=(const MyArray& arr) {
//先判断原来的堆区有没有数据
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Size = 0;
this->m_capacity = 0;
}
//深拷贝(again)
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];
//一一赋值
}
return *this;
//为了能够链式操作,需要返回的是自身(地址)
}
//尾插法
void Push_back_Tail(const T &val) {
//判断容量是否已经等于大小了{
if (this->m_capacity == this->m_Size) {
return;
}
this->pAddress[this->m_Size] = val;
this->m_Size++;
}
//尾删法
void delete_back_Tail() {
if (this->m_Size == 0) {
return;
}
this->m_Size--;//直接修改长度就可以
}
//返回容量
int getcapacity() {
return m_capacity;
}
//返回数组大小
int getSize() {
return m_Size;
}
//可以使用括号按照下标来访问该数据
//返回一个T类型的数据,作为左值必须加一个引用
T& operator[](int index) {
return this->pAddress[index];
}
//重写>>
T& operator<<(int index){
return index;
}
void printIntArray(MyArray <int>& arr) {
for (int i = 0; i < (arr.getSize()); i++) {
cout << arr[i] << endl;
}
}
//析构函数
~MyArray() {
if (this->pAddress != NULL) {
delete[]this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress;//指针指向堆区开辟的真实数组(真正的数组存在堆区)
int m_capacity;//容量
int m_Size;//数组实际长度
};
c++基本就要学完了,加油!