小甲鱼C++快速入门全套48节视频及配套PPT、配套所有源码和推荐C++学习电子书籍等C++资料已整理。
目录:
第一讲:C++语言与OO思想介绍
第二讲:从一个小程序说起
作业1:整型数组求和
作业2:求带任意空格的输入整数之和
第三讲:输入输出方法
实例1:忽略输入字符串的前面部分字符输出
实例2:打印输入的一段文本,回车结束
实例3:打印输入的前20个字符
作业1:对3开方不同精度输出
作业2:对于输入的字符串,每次提取其四个字符,以不同输出宽度输出
第四讲:文件操作
实例1:文件读取(读取同一文件夹的test.txt文件内容)
实例2:将0-9写入到同一文件夹的test.txt文件
实例3:以添加形式将10-0写入test.txt文件
实例4:同时完成对test.txt的读写
作业:将text1.txt文件内容复制到text2.txt中
第五讲:输入输出小结
实例1:根据输入内容输出
实例2:摄氏、华氏温度转换
第六讲:函数的重载
实例1:改变同一函数的输入参数类型
作业:calc()传入不同数目的参数时,不同运算的运用
实例2:打印出用户输入的字符串
实例3:实例1的规范改进
第七讲:复杂的数据类型
第八讲:复杂的数据类型——指针
第九讲:复杂的数据类型——指针02
实例1:通过指针形式修改变量值(指针的解引用)
第十讲:复杂的数据类型——指针和数组
实例1:数组元素地址打印
实例2:数组重载
实例3:泛型数组重载
第十一讲:结构
实例1:简单数据库读写
第十二讲:传值、传址和传引用
实例1:值传递
实例2:指针地址传递
实例3:两值互换
实例4:两值互换2
实例5:不用指针的两值交换
第十三讲:联合、枚举和类型别名
实例1:联合的应用
实例2:枚举的应用
第十四节:对象
实例1:对象应用造一辆车
第十五讲:构造器和析构器
实例1:构造器的运用
实例2:构造器与析构器的共同应用
第十六讲:this指针和类的继承
实例1:子类与基类的运用
第十七讲:继承机制中的构造器和析构器
实例1:构造器及其参数的继承应用
实例2:构造器与析构器的综合运用
第十八讲:访问控制
实例1:访问级别应用
第十九讲:覆盖方法和重载方法
实例1:覆盖应用
实例2:重载应用
实例3:子类内声明重载
第二十讲:一种特殊的友情关系——友元关系
实例1:友元关系访问保护量
第二十一讲:静态属性和静态方法
实例1:静态变量实例
第二十二讲:静态属性和静态方法2
实例1:this指针应用
第二十三讲:虚方法
实例:虚方法的运用
第二十四讲:抽象方法抽象方法(abstract method,也可以成为纯虚函数)
实例1:抽象方法应用
实例2:析构函数解析
第二十五讲:运算符的重载
实例1:复数加法
实例2:复数加法2
作业:分数加减乘除的操作符重载
第二十六讲:运算符重载2
第二十七讲:运算符左移<<重载
实例:左移操作符重载
第二十八讲:多继承(multiple inheritance)
第二十九讲:虚继承
实例:虚继承应用
第三十讲:错误处理和调试
第三十一讲:错误处理与调试2
实例:范围限制
第三十二讲:assert函数和捕获异常
实例1:assert函数应用
实例2:捕获异常(try,catch,throw)
第三十三讲:动态的内存管理
实例:动态内存管理
第三十四讲:动态数组
实例:动态数组
第三十五讲:从函数或方法返回内存
实例1:函数或方法返回内存
实例2:函数指针
第三十六讲:副本构造器
实例1:带指针变量的等号重载
实例2:副本构造器
第三十七讲:高级强制类型转换
实例1:静态强制转换
实例2:动态强制转换
第三十八讲:避免内存泄漏
第三十九讲:命名空间和模块化编程
实例:头文件应用
第四十讲:命名空间与模块化编程2
第四十一讲:命名空间
第四十二讲:链接和作用域
第四十三讲:链接和作用域2
第四十四讲:函数模板swap使用
实例:函数模板
第四十五讲:类模板
实例:栈的出入栈
第四十六讲:内联模板
实例:栈
第四十七讲:容器和算法
实例:向量容器应用
第四十八讲:向量和容器2
实例:迭代器指针应用
第一讲:C++语言与OO思想介绍
OO思想:每个对象都是一个完整的独立的个体,由相关的属性和行为组合与外界分隔。其思想就是将一切事物都看做一个对象,由于一个再复杂的模型结构都是由千千万万个对象组成的。从而使程序员不再面对一个个函数与变量,而是放眼全局,面对一个个对象。
OO思想的特点:
1封装:把对象的属性和方法结合成一个独立的系统单位,并尽可能隐藏内部细节
2抽象:对具体问题进行概括、对一类公共问题进行统一描述的过程
3继承:子类对象拥有与其基类相同的全部属性和方法
4多态:在基类定义的属性和行为被子类继承后可以具有不同的数据类型或者表现行为等特性
第二讲:从一个小程序说起
cout(cout << i表示变量i流向屏幕显示)是一个输出流对象,属于basic_ostream类的对象。ostream类在iostream头文件中定义。同理cin(回车后,键盘输入缓冲区内容流向cin流的内部缓冲区,cin >> xx操作便从这个缓冲区提取数据,即键盘输入流向程序)为输入流对象,
C++标准库所使用的所有标识符(即类、函数、对象等的名称)都是在一个特殊的名字空间(std)中来定义。
C++允许在程序的任意位置声明变量
作业1:整型数组求和
#include <iostream> //iostream C92 C99
using namespace std;//名字空间,cout与cin都在其里面
int addArray( int *array, int n );
int main()
{
int data[] = {0, 1, 2, 3, 4, 5,6, 7, 8, 9};
int size = sizeof(data) / sizeof(data[0]);
std::cout << "结果是: " << addArray(data, size) << std::endl;//std:cout表示cout在std里面,类似结构体
return 0;
}
int addArray( int *array, int n )
{
int sum = 0;
int i;
for( i=0; i < n; i++ )
{
sum += *array++;
}
return sum;
}
作业2:求带任意空格的输入整数之和
#include <iostream> //
using namespace std;//名字空间
int main()
{
int sum=0;
int i;
cout<<"请输入一串整数和任意数目的空格:";
while(cin>>i)//正常输入时
{
sum+=i;
while(cin.peek()==' ')//屏蔽空格
{
cin.get();
}
if(cin.peek()=='\n')
{
break;//跳出while循环
}
}
cout<<"结果是:"<<sum<<endl;
return 0;
}
第三讲:输入输出方法
实例1:忽略输入字符串的前面部分字符输出
#include <iostream> //
using namespace std;//名字空间
int main()
{
char buf[20];//只能存放19个字符,因为字符串以0结尾
cin.ignore(7);//忽略输入的前七个字符
cin.getline(buf,10);//获取10个字符存放在buf中,默认字符串以0结尾
cout<<buf<<endl;//endl表示清空缓存区
return 0;
}
实例2:打印输入的一段文本,回车结束
#include <iostream> //
using namespace std;//名字空间
int main()
{
char p;
cout<<"请输入一段文本:\n";
while(cin.peek()!='\n')//peek偷看输入的数据,不修改
{
cout<<(p=cin.get());//cin.get表示获取输入的一个字符
}
cout<<endl;//endl表示清空缓存区
return 0;
}
实例3:打印输入的前20个字符
#include <iostream> //
using namespace std;//名字空间
int main()
{
const int SIZE=50;
char buf[SIZE];
cout<<"请输入一段文本:";
cin.read(buf,20);//读取20个字符到buf缓冲区中
cout<<"字符串收集到的字符数为:"<<cin.gcount()<<endl;//计算提取到的字符数
cout<<"输入的文本信息是:";
cout.write(buf,20); //从缓冲区输出20个字符
cout<<endl;//endl表示清空缓存区
return 0;
}
作业1:对3开方不同精度输出
#include <iostream> //
#include <math.h>
using namespace std;//名字空间
int main()
{
double result=sqrt(3.0);
cout<<"对3开方保留小数点后0~9位,结果如下:\n\n"<<result;
for(int i=0;i<=9;i++)
{
cout.precision(i);
cout<<result<<endl;
}
cout<<"当前输出的精度为:"<<cout.precision()<<endl;
return 0;
}
作业2:对于输入的字符串,每次提取其四个字符,以不同输出宽度输出
#include <iostream> //
#include <math.h>
using namespace std;//名字空间
int main()
{
int width = 4;
char str[20];
cout<<"请输入一段文本:\n";
cin.width(5);//一次提取4个字符,因为最后有一个回车
while(cin >> str)
{
cout.width(width++);//设置不同的右对齐输出宽度
cout<<str<<endl;
cin.width(5);
}
return 0;
}
第四讲:文件操作
ifream与ofream分别为文件读取类和文件写入类
实例1:文件读取(读取同一文件夹的test.txt文件内容)
#include <fstream>// 涉及到了文件流操作
#include <iostream>
using namespace std;
int main()// in输入:读 out输出:写
{
ifstream in;// 用ifstream(文件读取类)这个类定义in这个对象,所以in拥有了这个对象所有功能性质
in.open( "test.txt" );// 打开这个文件
if( !in )// 打开失败
{
cerr << "打开文件失败" << endl;
return 0;
}
char x;
while( in >> x )//文件in流到字符x中去,每次流一个字符
{
cout << x;
}
cout << endl;
in.close();//关闭文件
return 0;
}
实例2:将0-9写入到同一文件夹的test.txt文件
#include <fstream>//涉及到了文件流操作
#include <iostream>
using namespace std;
int main()
{
ofstream out;//ofstream(文件输出类,写文件)
out.open( "test.txt" );
if( !out )
{
cerr << "打开文件失败!" << endl;
return 0;
}
for( int i=0; i < 10; i++ )
{
out << i;
}
out << endl;
out.close();
return 0;
}
实例3:以添加形式将10-0写入test.txt文件
#include <fstream>
#include <iostream>
using namespace std;
int main()//ofstream 为构造函数
{
ofstream out( "test.txt", ios::app );// app表示以添加形式(即不覆盖原数据)打开
if( !out )
{
cerr << "文件打开失败!" << endl;
return 0;
}
for( int i=10; i > 0; i-- )
{
out << i;
}
out << endl;
out.close();
return 0;
}
实例4:同时完成对test.txt的读写
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
fstream fp("test.txt", ios::in | ios::out );
if( !fp )
{
cerr << "打开文件失败!" << endl;
return 0;
}
fp << "IloveFishc.com!IloveFishc.com!";
static char str[100];
fp.seekg(ios::beg); //使得文件指针指向文件头 ios::end则是文件尾。
fp >> str;
cout << str << endl;
fp.close();
return 0;
}
作业:将text1.txt文件内容复制到text2.txt中
#include <fstream>//涉及到了文件流操作
#include <iostream>
using namespace std;
int main()
{
ifstream in;
ofstream out;
char x;
in.open("text1.txt");
out.open("text2.txt");
while(!in)
{
cout<<"源文件打开失败,请重新输入路径:";
return 0;
}
while(!out)
{
cout<<"目标文件失败,请重新输入路径:";
return 0;
}
while(in>>x)
{
out<<x;
}
out<<endl;
in.close();//关闭文件
out.close();//关闭文件
system("pause");
return 0;
}
第五讲:输入输出小结
实例1:根据输入内容输出
#include<iostream>
using namespace std;//名字空间
int main()
{
char answer;
cout << "请问可以格式化您的硬盘吗?!【Y/N】" << "\n";
cin >> answer;
switch(answer)
{
case'Y':
case'y':
cout <<"随便格式化硬盘是不好的,会让妈妈骂的~"<<"\n";
break;
case'N':
case'n':
cout << "您的选择是明智的!" << "\n";
break;
default:
cout << "您的输入不符合要求!!!" << "\n";
break;
}
std::cin.ignore(100,'\n');//忽略带回车的100个输入字符
std::cout << "输入任何字符结束程序!" << "\n";
std::cin.get();
return 0;
}
实例2:摄氏、华氏温度转换
//温度转换 华氏温度 = 摄氏温度 * 9.0/5.0 + 32
//const与define作用相同,都为定义常亮,但尽量用const
#include <iostream>
using namespace std;
int main()
{
const unsigned short ADD_SUBTRACT = 32;
const double RATI0 = 9.0 / 5.0;
double tempIn,tempOut;//输入输出数据
char typeIn,typeOut;//输入输出类型(摄氏度F或华氏温度C)
std::cout << "请亲们以【xx.x C】或者【xx.x F】格式输入一个温度:";
std::cin >> tempIn >> typeIn;
switch(typeIn)
{
case 'c':
case 'C':
tempOut = tempIn * RATI0 +32;
typeIn = 'C';
typeOut = 'F';
break;
case 'f':
case 'F':
tempOut = (tempIn - 32)/RATI0;
typeIn = 'F';
typeOut = 'C';
break;
default:
typeOut = 'E';
}
if(typeOut != 'E')
{
cout <<"\n"<< tempIn << typeIn << " = " << tempOut << typeOut << "\n\n";
}
else
{
cout << "输入错误!\n";
}
cin.ignore(100,'\n');//忽略带回车的100个输入字符
std::cout << "输入任何字符结束程序!" << "\n";
std::cin.get();
return 0;
}
第六讲:函数的重载
函数重载:实质就是用同样的名字再定义一个有着不同参数类型及个数来实现不同操作的函数。
实例1:改变同一函数的输入参数类型
#include <iostream>
void convertTemperature(double tempIn, char typeIn);
void convertTemperature(int tempIn, char typeIn);
int main()
{
double tempIn;
int tempInInt;
char typeIn;
std::cout << "请以【xx.x C】或【xx.x F】的形式输入温度: ";
std::cin >> tempIn >> typeIn;
std::cin.ignore(100, '\n');
std::cout << "\n";
convertTemperature(tempIn, typeIn);
std::cout << "请以【xx C】或【xx F】的形式输入温度: ";
std::cin >> tempInInt >> typeIn;
std::cin.ignore(100, '\n');
std::cout << "\n";
convertTemperature(tempInInt, typeIn);
return 0;
}
void convertTemperature(double tempIn, char typeIn)
{
const unsigned short ADD_SUBTRACT = 32;
const double RATIO = 9.0 / 5.0;
double tempOut;
char typeOut;
switch( typeIn )
{
case 'C':
case 'c':
tempOut = (tempIn * RATIO) + ADD_SUBTRACT;
typeOut = 'F';
typeIn = 'C';
break;
case 'F':
case 'f':
tempOut = (tempIn - ADD_SUBTRACT) / RATIO;
typeOut = 'C';
typeIn = 'F';
break;
default:
typeOut = 'E';
break;
}
if( typeOut != 'E' )
{
std::cout << tempIn << typeIn << " = " << tempOut << typeOut << "\n\n";
}
else
{
std::cout << "请按照给出格式输入!" << "\n\n";
}
std::cout << "请输入任意字符结束!" << "\n";
std::cin.get();
}
void convertTemperature(int tempIn, char typeIn)
{
const unsigned short ADD_SUBTRACT = 32;
const double RATIO = 9.0 / 5.0;
int tempOut;
char typeOut;
switch( typeIn )
{
case 'C':
case 'c':
tempOut = (tempIn * RATIO) + ADD_SUBTRACT;
typeOut = 'F';
typeIn = 'C';
break;
case 'F':
case 'f':
tempOut = (tempIn - ADD_SUBTRACT) / RATIO;
typeOut = 'C';
typeIn = 'F';
break;
default:
typeOut = 'E';
break;
}
if( typeOut != 'E' )
{
std::cout << tempIn << typeIn << " = " << tempOut << typeOut << "\n\n";
}
else
{
std::cout << "请按照给出格式输入!" << "\n\n";
}
std::cout << "请输入任意字符结束!" << "\n";
std::cin.get();
}
作业:calc()传入不同数目的参数时,不同运算的运用
#include <iostream>
double calc(double tempIn);//计算该参数平方值
double calc(double tempIn, double tempIn2);//计算两个参数的积
double calc(double tempIn, double tempIn2, double tempIn3);//计算三个参数的和
int main()
{
double tempIn,tempIn2,tempIn3;
int number;
double tempOut;
std::cout << "请输入数据个数: ";
std::cin >> number;
std::cout << "请以【xx xx】形式输入具体数据: ";
switch(number)
{
case 1:
std::cin >> tempIn;
tempOut = calc(tempIn);
break;
case 2:
std::cin >> tempIn >> tempIn2;
tempOut = calc(tempIn, tempIn2);
break;
case 3:
std::cin >> tempIn >> tempIn2 >> tempIn3;
tempOut = calc(tempIn, tempIn2, tempIn3);
break;
default:
std::cout << "错误!";
break;
}
std::cout << "计算结果为:" << tempOut << "\n";
std::cin.ignore(100, '\n');
std::cout << "\n";
return 0;
}
double calc(double tempIn)
{
return tempIn*tempIn;
}
double calc(double tempIn, double tempIn2)
{
return tempIn*tempIn2;
}
double calc(double tempIn, double tempIn2, double tempIn3)
{
return (tempIn + tempIn2 + tempIn3);
}
第七讲:复杂的数据类型
数组:可以把许多个同类型的值存储在同一变量名下
实例1:输入的数据存储到数组中,并计算其和与平均值输出
#include <iostream>
using namespace std;//使用作用域
int main()
{
int array[10];
int i;
int sum=0;
double average;
cout << "请输入10个整数!\n";
for(i=0;i<10;i++)
{
cout << "请输入第" << i+1 << "个整数:";
cin >> array[i];
sum+=array[i];
}
average = sum/10.0;
cout << "和为:" << sum << "\n" << "平均值为:" << average << "\n";
return 0;
}
实例2:打印出用户输入的字符串
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str;
cout << "请输入一个字符串:";
getline(cin, str);//getline为获取一行数据 cin >> str使用时遇到空格便默认输入接收
cout << str;
return 0;
}
实例3:实例1的规范改进
#include <iostream>
//未完善
using namespace std;//使用作用域
//当输入错误时,cin会返回错误0
#define ITEM 10
int main()
{
int array[ITEM];
int i;
int sum=0;
double average;
cout << "请输入" << ITEM << "个整数!\n";
for(i=0;i<ITEM;i++)
{
cout << "请输入第" << i+1 << "个整数:";
while(!(cin >> array[i])) //输入非法时是下一次输入时才进入循环
{
cin.clear();//清楚不规范输入值
cin.ignore(100,'\n');
cout << "\n请输入一个合法的值!";
cout << "\n请重新输入第" << i+1 << "个整数:\n";
cin >> array[i];
}
sum+=array[i];
}
average = (double)sum/ITEM;
cout << "和为:" << sum << "\n" << "平均值为:" << average << "\n";
return 0;
}
第八讲:复杂的数据类型——指针
小知识:程序以文件的形式存储在硬盘,但它们却是在计算机的内存中运行
对于变量可以通过变量名与地址两种方式进行索引,变量的地址在程序执行期间是不会发生变换的
地址是计算机内存中的某个位置;指针是专门用来存放地址的特殊类型变量
第九讲:复杂的数据类型——指针02
指针的类型必须与由它保存其地址的变量的类型一致,当某个变量的地址给了指针p时,就可以通过*p(即表示该地址的数据)来对该变量数据进行操作
一定要牢记一个事实:指针所保存的是内存中的一个地址,它并不保存指向的数据的值本身。因此务必确保指针对应一个已经存在的变量或者一块已经分配的内存
*有两种用途,一是创建指针: int *p = &a; 另外是解引用 *p = 123;
c++允许多个指针指向同一个地址
.void *则为“无类型指针”,可以指向任何数据类型。对一个无类型指针进行解引用前必须先将他转换为一个适当的数据类型。
实例1:通过指针形式修改变量值(指针的解引用)
#include <iostream>
using namespace std;
int main()
{
int a = 123;
float b = 3.14;
char c = 'c';
unsigned long d = 19880808;
string e = "I love FishC!";
cout << "a的值是:" << a << "\n";
cout << "b的值是:" << b << "\n";
cout << "c的值是:" << c << "\n";
cout << "d的值是:" << d << "\n";
cout << "e的值是:" << e << "\n\n";
int *aPointer = &a;
float *bPointer = &b;
char *cPointer = &c;
unsigned long *dPointer = &d;
string *ePointer = &e;
*aPointer = 456;
*bPointer = 4.13;
*cPointer = 'F';
*dPointer = 20190525;
*ePointer = "I love Beauty!";
cout << "a的值是:" << a << "\n";
cout << "b的值是:" << b << "\n";
cout << "c的值是:" << c << "\n";
cout << "d的值是:" << d << "\n";
cout << "e的值是:" << e << "\n\n";
}
第十讲:复杂的数据类型——指针和数组
计算机是把数组以一组连续的内存块保存的。
数组的第一个元素的地址为该数组的基地址。
实例1:数组元素地址打印
#include <iostream>
using namespace std;
int main()
{
const unsigned short ITEMS = 5;
int intArray[ITEMS] = {1,2,3,4,5};
char charArray[ITEMS] = {'F','i','s','h','C'};
int *intPtr = intArray;
char *charPtr = charArray;
cout << "整型数组输出:" << '\n';
for(int i=0;i < ITEMS; i++)
{
std::cout << *intPtr << " at " << (intPtr) << '\n';//用reinterpret_cast把指针类型强制转换为unsigned int
intPtr++;//地址加上以其所定义的类型所占的字节
}
cout << "字符型数组输出:" << '\n';
for(int i=0;i < ITEMS; i++)
{
cout << *charPtr << " at " << (charPtr) << '\n';
charPtr++;
}
return 0;
}
实例2:数组重载
#include <iostream>
using namespace std;
void print(int *pBegin,int *pEnd)
{
while(pBegin != pEnd)
{
cout << *pBegin;
++pBegin;//地址加1
}
}
void print(char *pBegin,char *pEnd)
{
while(pBegin != pEnd)
{
cout << *pBegin;
++pBegin;//地址加1
}
}
int main()
{
int num[5] = {0,1,2,3,4};
char name[5] = {'F','i','s','h','C'};
print(num,num + 5);
cout << '\n';
print(name,name + 5);
cout << '\n';
return 0;
}
实例3:泛型数组重载
#include <iostream>
using namespace std;
template <typename elemType>//将输入参数typename的类型赋值给elemType
void print(elemType *pBegin,elemType *pEnd)
{
while(pBegin != pEnd)
{
cout << *pBegin;
++pBegin;//地址加1
}
}
int main()
{
int num[5] = {0,1,2,3,4};
char name[5] = {'F','i','s','h','C'};
print(num,num + 5);
cout << '\n';
print(name,name + 5);
cout << '\n';
return 0;
}
第十一讲:结构
结构是一种由程序员自己定义的、由其他变量类型组合而成的数据类型。其所能包含的变量的个数是没有限制的。
实例1:简单数据库读写
#include <iostream>
#include <fstream> //文件操作
#include <windows.h> // 为了使用Sleep()函数
struct FishOil
{
std::string name;
std::string uid;
char sex;
};
bool InitFishC();
bool ReadFishC();
void RecordFishC();
bool WriteFishC(FishOil *OilData);
int main()
{
int i;
InitFishC(); // 初始化数据。
while( 1 )
{
std::cout << "请选择需要进行的操作: \n";
std::cout << "1. 打印数据到屏幕\n";
std::cout << "2. 录入数据\n";
std::cout << "3. 退出程序\n";
std::cin >> i;
switch( i )
{
case 1:
if( ReadFishC() )
std::cout << "成功读取文件^_^\n\n";
else
std::cout << "读取文件失败T_T\n\n";
break;
case 2:
RecordFishC();
break;
case 3:
return 0;
}
}
return 0;
}
bool InitFishC()
{
FishOil OilInit = {"小甲鱼", "fishc00001", 'M'};
if( WriteFishC(&OilInit) == false )
std::cout << "初始化失败T_T\n";
}
bool ReadFishC() //读文件
{
std::string temp;
std::ifstream fileInput("FishC.txt");
if( fileInput.is_open() )
{
std::cout << "\n正在输出记录数据...... ";
for( int i=0; i <= 100; i++ ) // 打印百分比
{
std::cout.width(3);
std::cout << i << "%";
Sleep(50);
std::cout << "\b\b\b\b";
}
std::cout << "\n\n";
std::cout << " 姓名 " << " 身份证 " << " 性别 " << "\n\n";
while( std::getline( fileInput, temp ) )
{
std::cout << temp << " ";
std::cout << "\n";
}
std::cout << "\n\n";
return true;
}
else
return false;
}
void RecordFishC()//录入数据
{
char goon, Save;
FishOil OilData;
FishOil *pOilData;
goon = 'Y';
while( 'Y' == goon )
{
std::cout << "请输入鱼C账号: ";
std::cin >> OilData.name;
std::cout << "请输入鱼C身份证:";
std::cin >> OilData.uid;
std::cout << "请输入性别:";
std::cin >> OilData.sex;
std::cout << "录入成功, 请问需要保存吗?(Y/N)";
std::cin >> Save;
if( 'Y' == Save )
{
pOilData = &OilData;
if( WriteFishC( pOilData ) )
std::cout << "成功写入文件^_^\n";
else
std::cout << "写入文件失败T_T\n";
}
else
{
return;
}
std::cout << "/n请问需要再次录入吗?(Y/N)";
std::cin >> goon;
}
}
bool WriteFishC( FishOil *pOilData )//写文件
{
std::ofstream fileOutput("FishC.txt", std::ios::app);
// std::ios::app用来说明在老数据追加新数据
if( fileOutput.is_open() )
{
fileOutput << pOilData->name << " ";
fileOutput << pOilData->uid << " ";
fileOutput << pOilData->sex << "\n";
fileOutput.close();
std::cout << "数据成功保存到FishC.txt\n\n";
return true;
}
else
std::cout << "保存失败T_T\n";
return false;
}
第十二讲:传值、传址和传引用
实例1:值传递
#include<iostream>
void changeAge(int age,int newAge);
int main()
{
int age = 24;//定义一个age,占一处地址
std::cout << "My age is " << age <<"\n";
changeAge(age,age + 1);
std::cout << "Now my age is " << age << "\n";
return 0;
}
void changeAge(int age,int newAge)//再定义一个age,占另一处地址
{
age = newAge;
std::cout << "In this, my age is " << age << "\n";
}
绕开“值传递”问题的第一种方法是向函数传递变量的地址取代他的值。
实例2:指针地址传递
#include<iostream>
void changeAge(int *age,int newAge);
int main()
{
int age = 24;//定义一个age,占一处地址
std::cout << "My age is " << age <<"\n";
changeAge(&age,age + 1);
std::cout << "Now my age is " << age << "\n";
return 0;
}
void changeAge(int *age,int newAge)//再定义一个age,占另一处地址
{
*age = newAge;
std::cout << "In this, my age is " << *age << "\n";
}
实例3:两值互换
#include<iostream>
void swap(int *x,int *y);
int main()
{
int x,y;
std::cout << "请输入两个不同的值:";
std::cin >> x >> y;
swap(&x,&y);
std::cout << "调换后输出:" << x << ' ' << y <<"\n\n";
}
void swap(int *x,int *y)
{
int temp;
temp = *x;
*x = *y;
*y = temp;
}
实例4:两值互换2
#include<iostream>
void swap(int *x,int *y);
int main()
{
int x,y;
std::cout << "请输入两个不同的值:";
std::cin >> x >> y;
swap(&x,&y);
std::cout << "调换后输出:" << x << ' ' << y <<"\n\n";
}
void swap(int *x,int *y)
{
*x ^= *y;
*y = *x;
*x ^= *y;
}
实例5:不用指针的两值交换
#include <iostream>
void swap(int &x,int &y);
int main()
{
int x,y;
std::cout << "请输入两个不同的值:";
std::cin >> x >> y;
swap(x,y);
std::cout << "调换后输出:" << x << ' ' << y << "\n\n";
return 0;
}
void swap(int &x,int &y)
{
int temp;
temp = x;
x = y;
y = temp;
}
第十三讲:联合、枚举和类型别名
联合(union)与结构有很多相似之处,联合也可以容纳多种不同类型的值,但是它每次只能储存这些值中的一个(即当我们已经给联合里的一个成员赋值后,再给另一个成员赋值时,将丢弃第一个成员的值)。
实例1:联合的应用
#include <iostream>
union mima
{
unsigned long birthday;
unsigned short ssn;
const char *pet;//将pet声明为一个指向不变字符串的指针
};
int main()
{
mima mima_1;
mima_1.birthday = 19970328;
std::cout << mima_1.birthday << "\n";
mima_1.pet = "Chaozai";//为字符串指针内的数据赋值
std::cout << mima_1.pet << "\n";
std::cout << mima_1.birthday << "\n";//由于覆盖,将输出pet中字符串存储的地址
return 0;
}
枚举(enum)类型用来创建一个可取值列表。枚举值不是字符串,编译器会按照各个枚举值在定义时出现的先后顺序把它们与0~n-1的整数(n是枚举值的总个数)分别关联起来。
实例2:枚举的应用
#include <iostream>
int main()
{
enum weekdays{Monday, Tuesday, Wednesday, Thursday, Friday};//编译后依次变为0、1、2、3、4
weekdays today;
today = Monday;
std::cout << today << "\n";
today = Friday;
std::cout << today << "\n";
switch (today)
{
case Monday:
std::cout << "hello";
break;
case Tuesday:
std::cout << "yiwofeiye";
break;
default:
std::cout << "Goodbye";
break;
}
return 0;
}
类型别名(typedef)可以为一个类型定义创建一个别名。例如:
定义typedef int* intPointer;后便可以使用intPointer myPointer;来定义整型指针。
第十四节:对象
对象本质上不过是一种新的数据类型。
类(class)是一个模型(就像是一张蓝图,它决定一个对象将拥有什么样的属性、功能等),且每个类跟变量一样都有一个名字。当我们为这个类创建实例的时候,也就是对象(类的具体化实现)。
类由变量(类里的变量称属性)和函数(类里的函数称方法)组成,对象将使用那些变量来存放信息,调用那些函数来完成操作。同理对象内部有变量和函数,而结构通常只由各种变量构成。
例如std::cout即为使用的是std类中的cout对象;std:string数据类型使用的是std类中的string对象
面相对象编程技术(object-oriented programming)可以说是面向过程技术(procedural programming)的替代品。
面向过程技术关注的是对数据进行处理的过程,面向对象(OOP技术)关注的是对数据进行怎样的处理。
实例1:对象应用造一辆车
#include <iostream>
#define FULL_GAS 85
class Car//让我们来造辆车,定义类Car ,C++允许在类里面声明常量,但不允许对它进行赋值
{
public:
std::string color;
std::string engine;
unsigned int gas_tank;//油缸
unsigned int wheel;
void setColor(std::string col);
void setEngine(std::string eng);
void setWheel(unsigned int whe);
void filltank(int liter);//加油
int running(void);//动
void warning(void);
};
void Car::setColor(std::string col)
{
color = col;
}
void Car::setEngine(std::string eng)
{
engine = eng;
}
void Car::setWheel(unsigned int whe)
{
wheel = whe;
}
void Car::filltank(int liter)//函数(又称方法)的定义
{
gas_tank += liter;
}
int Car::running(void)
{
std::cout << "我正在以120的时速往前移动。。。\n";
gas_tank--;
std::cout << "当前还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!\n";
return gas_tank;
}
void Car::warning(void)
{
std::cout << "WARNING!!" << "\n还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!\n";
}
int main()
{
char i;
Car mycar, car1;
mycar.setColor("WHITE");
mycar.setEngine("V8");
mycar.setWheel(4);
mycar.gas_tank = FULL_GAS;
while(mycar.running() )
{
if(mycar.gas_tank < 10)
{
mycar.warning();
std::cout << "请问是否需要加满油再行驶?(Y/N)\n";
std::cin >> i;
if( 'Y'== i || 'y' == i)
{
mycar.filltank(FULL_GAS);
}
}
}
return 0;
}
第十五讲:构造器和析构器
构造器和通常方法的主要区别:
1、构造器的名字必须和它所在类的名字一样
2、系统在创建某个类的对象时会第一时间自动调用这个类的构造器
3、构造器永远不会返回任何值
实例1:构造器的运用
#include <iostream>
#define FULL_GAS 85
class Car//让我们来造辆车,定义类Car ,C++允许在类里面声明常量,但不允许对它进行赋值
{
public:
std::string color;
std::string engine;
unsigned int gas_tank;//油缸
unsigned int wheel;
Car(void);//类Car的构造函数,系统会自动调用
void setColor(std::string col);
void setEngine(std::string eng);
void setWheel(unsigned int whe);
void filltank(int liter);//加油
int running(void);//动
void warning(void);
};
Car::Car(void)
{
color = "White";
engine = "V8";
wheel = 4;
gas_tank = FULL_GAS;
}
void Car::setColor(std::string col)
{
color = col;
}
void Car::setEngine(std::string eng)
{
engine = eng;
}
void Car::setWheel(unsigned int whe)
{
wheel = whe;
}
void Car::filltank(int liter)//函数(又称方法)的定义
{
gas_tank += liter;
}
int Car::running(void)
{
char i;
std::cout << "我正在以120的时速往前移动。。。\n";
gas_tank--;
std::cout << "当前还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!\n";
if(gas_tank < 10)
{
warning();
std::cout << "请问是否需要加满油再行驶?(Y/N)\n";
std::cin >> i;
if( 'Y'== i || 'y' == i)
{
filltank(FULL_GAS);
}
if(gas_tank == 0)
{
std::cout << "抛瞄中。。。。。";
return 0;
}
}
return gas_tank;
}
void Car::warning(void)
{
std::cout << "WARNING!!" << "\n还剩 " << 100*gas_tank/FULL_GAS << "%" << "油量!\n";
}
int main()
{
Car mycar;
mycar.gas_tank = FULL_GAS;
while(mycar.running())//有油则继续跑,没有油则结束
{
;
}
return 0;
}
在销毁一个对象时,系统会调用另一个特殊方法,即析构器。
一般来说,构造器用来完成事先的初始化和准备互作(申请分配内存);析构器用来完成事后所必须的清理工作(清理内存)
析构器不返回任何值,也不带参数
实例2:构造器与析构器的共同应用
#include <iostream>
#include <string>
#include <fstream>
class StoreQuote//定义类StoreQuote
{
public:
std::string quote,speaker;
std::ofstream fileOutput;//定义文件对象fileOutput
StoreQuote();//构造器
~StoreQuote();//析构器
void inputQuote();//名言输入
void inputSpeaker();//作者输入
bool write();//写文件
};
StoreQuote::StoreQuote()
{
fileOutput.open("test.txt", std::ios::app);//以附加(app)的形式打开文件
}
StoreQuote::~StoreQuote()//关闭文件,释放内存
{
fileOutput.close();
}
void StoreQuote::inputQuote()
{
std::getline(std::cin, quote);
}
void StoreQuote::inputSpeaker()
{
std::getline(std::cin, speaker);
}
bool StoreQuote::write()
{
if(fileOutput.is_open())//文件是否打开成功
{
fileOutput << quote << "|" << speaker << "\n";//写入数据
return true;
}
else
{
return false;
}
}
int main()
{
StoreQuote quote;//使用StoreQuote类定义一个对象quote
std::cout << "请输入一句名言:\n";
quote.inputQuote();
std::cout << "请输入作者:\n";
quote.inputSpeaker();
if(quote.write())//文件是否写入成功
{
std::cout << "成功写入文件";
}
else
{
std::cout << "写入文件失败";
return 1;
}
return 0;
}
第十六讲:this指针和类的继承
this指针指的是指向当前类生成的对象
继承机制使得程序员可以创建一个类的堆叠层次结构,每个子类均将继承在它的基类定义的方法和属性。
简单地说,就是通过继承机制,可以对现有的代码进行进一步扩展,并应用到新的程序中。
基类可以派生出其它的类,也称为父类或超类(如动物类)
子类是从基类派生出来的类(如乌龟类、猪类)
实例1:子类与基类的运用
#include <iostream>
#include <string>
class Animal//定义Animal类
{
public:
std::string mouth;
void eat();
void sleep();
void drool();//流鼻涕
};
class Pig:public Animal//类Pig继承于类Animal
{
public:
void climb();
};
class Turtle:public Animal//类Turtle继承于类Animal
{
public:
void swim();
};
void Animal::eat()//Animal类中的方法函数
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的会流口水 流口水。。。" << std::endl;
}
void Pig::climb()//类Pig中的climb方法函数
{
std::cout << "我是一只漂亮的小母猪猪,我会上树,邱。。。" << std::endl;
}
void Turtle::swim()//类Turtle中的swim方法函数
{
std::cout << "我是一只小甲鱼,当母猪抓我,我就游到海里,哈。。。" << std::endl;
}
int main()
{
Pig pig;
Turtle turtle;//定义一个对象turtle
pig.eat();
turtle.eat();
pig.climb();
turtle.swim();
return 0;
}
第十七讲:继承机制中的构造器和析构器
实例1:构造器及其参数的继承应用
#include <iostream>
#include <string>
class Animal//定义Animal类
{
public:
std::string mouth;
std::string name;
Animal(std::string theName);//类Animal的构造器
void eat();
void sleep();
void drool();//流鼻涕
};
Animal::Animal(std::string theName)//类Animal构造器函数
{
name = theName;
}
class Pig:public Animal//类Pig继承于类Animal
{
public:
Pig(std::string theName);//类Pig的构造器
void climb();
};
Pig::Pig(std::string theName):Animal(theName)//类Pig的构造函数继承于类Animal的构造函数
{
}
class Turtle:public Animal//类Turtle继承于类Animal
{
public:
Turtle(std::string theName);//类Pig的构造器
void swim();
};
Turtle::Turtle(std::string theName):Animal(theName)//类Turtle的构造函数继承于类Animal的构造函数参数
{
}
void Animal::eat()//Animal类中的方法函数
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的会流口水 流口水。。。" << std::endl;
}
void Pig::climb()//类Pig中的climb方法函数
{
std::cout << "我是一只漂亮的小母猪猪,我会上树,邱。。。" << std::endl;
}
void Turtle::swim()//类Turtle中的swim方法函数
{
std::cout << "我是一只小甲鱼,当母猪抓我,我就游到海里,哈。。。" << std::endl;
}
int main()
{
Pig pig("小猪猪");//定义一个对象pig,由于其继承于类Animal,
//且构造函数也继承于类Animal的构造函数,所以参数"小猪猪"将赋值给类pig中的名字属性name
Turtle turtle("小甲鱼");//定义一个对象turtle
std::cout << "这只猪的名字是:" << pig.name << std::endl;
std::cout << "每只乌龟都有个伟大的名字:" << turtle.name << std::endl;
pig.eat();
turtle.eat();
pig.climb();
turtle.swim();
return 0;
}
实例2:构造器与析构器的综合运用
#include <iostream>
#include <string>
//由于继承,编译后将首先调用基类构造器,然后调用子类构造器,然后运行其它程序;
//运行到程序末尾时,先调用子类析构器,让后调用基类析构器。
class BaseClass//定义基类BaseClass
{
public:
BaseClass();//构造器
~BaseClass();//析构器
void doSomething();
};
class SubClass : public BaseClass定义子类SubClass,类SubClass继承于类BaseClass
{
public:
SubClass();
~SubClass();
};
BaseClass::BaseClass()//类BaseClass构造器函数
{
std::cout << "进入基类构造器。。。\n";
std::cout << "我在基类构造器里边干了某些事。。。\n\n";
}
BaseClass::~BaseClass()//类BaseClass析构器函数
{
std::cout << "进入基类析构器。。。\n";
std::cout << "我在基类析构器里边干了某些事。。。\n\n";
}
void BaseClass::doSomething()//基类的doSomething函数
{
std::cout << "我干了某些事。。。\n\n";
}
SubClass::SubClass()//子类SubClass的构造函数
{
std::cout << "进入子类构造器。。。\n";
std::cout << "我在子类构造器里面还干了某些事。。。\n\n";
}
SubClass::~SubClass()//子类SubClass的析构函数
{
std::cout << "进入子类析构器。。。\n";
std::cout << "我在子类析构器里面还干了某些事。。。\n\n";
}
int main()
{
SubClass subclass;
subclass.doSomething();
std::cout << "完事,收工!\n";
return 0;
}
第十八讲:访问控制
访问控制:C++提供用来保护(保护意思是对谁可以调用某个方法和访问某一个属性加上一个限制)类里的方法和属性的手段
实例1:访问级别应用
//级别 允许谁来访问
//public 任何代码
//protected 这个类本身和它的子类
//private 只有这个类本身
#include <iostream>
#include <string>
class Animal//定义Animal类
{
public:
std::string mouth;
//std::string name;
Animal(std::string theName);//类Animal的构造器
void eat();
void sleep();
void drool();//流鼻涕
protected:
std::string name;
};
Animal::Animal(std::string theName)//类Animal构造器函数
{
name = theName;
}
class Pig:public Animal//类Pig继承于类Animal
{
public:
Pig(std::string theName);//类Pig的构造器
void climb();
};
Pig::Pig(std::string theName):Animal(theName)//类Pig的构造函数继承于类Animal的构造函数
{
}
class Turtle:public Animal//类Turtle继承于类Animal
{
public:
Turtle(std::string theName);//类Pig的构造器
void swim();
};
Turtle::Turtle(std::string theName):Animal(theName)//类Turtle的构造函数继承于类Animal的构造函数参数
{
}
void Animal::eat()//Animal类中的方法函数
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的会流口水 流口水。。。" << std::endl;
}
void Pig::climb()//类Pig中的climb方法函数
{
std::cout << "我是一只漂亮的小母猪猪,我会上树,邱。。。" << std::endl;
}
void Turtle::swim()//类Turtle中的swim方法函数
{
std::cout << "我是一只小甲鱼,当母猪抓我,我就游到海里,哈。。。" << std::endl;
}
int main()
{
Pig pig("小猪猪");//定义一个对象pig,由于其继承于类Animal,
//且构造函数也继承于类Animal的构造函数,所以参数"小猪猪"将赋值给类pig中的名字属性name
Turtle turtle("小甲鱼");//定义一个对象turtle
//pig.name = "小甲鱼"; //由于name被保护起来,所以赋值将报错
std::cout << "这只猪的名字是:" << pig.name << std::endl;
std::cout << "每只乌龟都有个伟大的名字:" << turtle.name << std::endl;
pig.eat();
turtle.eat();
pig.climb();
turtle.swim();
return 0;
}
使用private的好处:可以只修改某个类的内部实现,而不必重新修改整个程序。因为其他代码根本就访问不到private保护的内容
在同一个类中可以使用多个public:,private和protected:语句,但最好把你的元素集中到一个地方,可读性会更好。
protected
把基类的访问级别改为protected,如果原来是public的话。这将使得这个子类外部的代码无法通过子类去访问基类中的public
private
是在告诉编译器从基类继承来的每一个成员都当做private来对待,这意味着只有这个子类可以使用它从基类继承来的元素。
第十九讲:覆盖方法和重载方法
覆盖是指派生类函数覆盖基类函数(函数名字参数相同且基类函数必须有virtual关键字)
以上学习可知可以通过创建新的子类来重用现有的代码(继承)
当我们需要在基类里提供一个通用的函数,但在它的某个子类里需要修改这个方法的实现,覆盖(overriding)就可以做到。
实例1:覆盖应用
//级别 允许谁来访问
//public 任何代码
//protected 这个类本身和它的子类
//private 只有这个类本身
#include <iostream>
#include <string>
class Animal//定义Animal类
{
public:
std::string mouth;
//std::string name;
Animal(std::string theName);//类Animal的构造器
void eat();
void sleep();
void drool();//流鼻涕
protected:
std::string name;
};
Animal::Animal(std::string theName)//类Animal构造器函数
{
name = theName;
}
class Pig:public Animal//类Pig继承于类Animal
{
public:
Pig(std::string theName);//类Pig的构造器
void climb();
void eat(); //new 再一次声明,以便原函数可以覆盖修改
};
Pig::Pig(std::string theName):Animal(theName)//类Pig的构造函数继承于类Animal的构造函数
{
}
class Turtle:public Animal//类Turtle继承于类Animal
{
public:
Turtle(std::string theName);//类Pig的构造器
void swim();
void eat(); //new 再一次声明,以便原函数可以覆盖修改
};
Turtle::Turtle(std::string theName):Animal(theName)//类Turtle的构造函数继承于类Animal的构造函数参数
{
}
void Animal::eat()//Animal类中的方法函数
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的会流口水 流口水。。。" << std::endl;
}
void Pig::climb()//类Pig中的climb方法函数
{
std::cout << "我是一只漂亮的小母猪猪,我会上树,邱。。。" << std::endl;
}
void Turtle::swim()//类Turtle中的swim方法函数
{
std::cout << "我是一只小甲鱼,当母猪抓我,我就游到海里,哈。。。" << std::endl;
}
void Pig::eat()//重新声明eat方法
{
Animal::eat();//可省去
std::cout << "我在吃鱼\n\n" << std::endl;
}
void Turtle::eat()//重新声明eat方法
{
Animal::eat();//可省去
std::cout << "我正在吃东坡肉!\n\n" << std::endl;
}
int main()
{
Pig pig("小猪猪");//定义一个对象pig,由于其继承于类Animal,
//且构造函数也继承于类Animal的构造函数,所以参数"小猪猪"将赋值给类pig中的名字属性name
Turtle turtle("小甲鱼");//定义一个对象turtle
//pig.name = "小甲鱼"; //由于name被保护起来,所以赋值将报错
// std::cout << "这只猪的名字是:" << pig.name << std::endl;
// std::cout << "每只乌龟都有个伟大的名字:" << turtle.name << std::endl;
pig.eat();
turtle.eat();
pig.climb();
turtle.swim();
return 0;
}
重载机制使你可以定义多个同名的方法(函数),只是它们的输入参数必须不同。(因为编译器是依靠不同的输入参数来区分不同的方法)
实例2:重载应用
//级别 允许谁来访问
//public 任何代码
//protected 这个类本身和它的子类
//private 只有这个类本身
#include <iostream>
#include <string>
class Animal//定义Animal类
{
public:
std::string mouth;
//std::string name;
Animal(std::string theName);//类Animal的构造器
void eat();
void eat(int eatCount);
void sleep();
void drool();//流鼻涕
protected:
std::string name;
};
Animal::Animal(std::string theName)//类Animal构造器函数
{
name = theName;
}
class Pig:public Animal//类Pig继承于类Animal
{
public:
Pig(std::string theName);//类Pig的构造器
void climb();
};
Pig::Pig(std::string theName):Animal(theName)//类Pig的构造函数继承于类Animal的构造函数
{
}
class Turtle:public Animal//类Turtle继承于类Animal
{
public:
Turtle(std::string theName);//类Pig的构造器
void swim();
};
Turtle::Turtle(std::string theName):Animal(theName)//类Turtle的构造函数继承于类Animal的构造函数参数
{
}
void Animal::eat()//Animal类中的方法函数
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::eat(int eatCount)
{
std::cout << "我吃了" << eatCount << "碗馄饨!\n";
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的会流口水 流口水。。。" << std::endl;
}
void Pig::climb()//类Pig中的climb方法函数
{
std::cout << "我是一只漂亮的小母猪猪,我会上树,邱。。。" << std::endl;
}
void Turtle::swim()//类Turtle中的swim方法函数
{
std::cout << "我是一只小甲鱼,当母猪抓我,我就游到海里,哈。。。" << std::endl;
}
int main()
{
Pig pig("小猪猪");//定义一个对象pig,由于其继承于类Animal,
//且构造函数也继承于类Animal的构造函数,所以参数"小猪猪"将赋值给类pig中的名字属性name
Turtle turtle("小甲鱼");//定义一个对象turtle
//pig.name = "小甲鱼"; //由于name被保护起来,所以赋值将报错
// std::cout << "这只猪的名字是:" << pig.name << std::endl;
// std::cout << "每只乌龟都有个伟大的名字:" << turtle.name << std::endl;
pig.eat();
turtle.eat();
pig.eat(15);
pig.climb();
turtle.swim();
return 0;
}
在对方法进行覆盖(注意区分覆盖和重载)时,一定要看仔细,因为只要声明的输入参数和返回值与原来的不一致,你编写出来的就将是一个重载的方法而不是覆盖方法。
实例3:子类内声明重载
//级别 允许谁来访问
//public 任何代码
//protected 这个类本身和它的子类
//private 只有这个类本身
#include <iostream>
#include <string>
class Animal//定义Animal类,基类
{
public:
std::string mouth;
//std::string name;
Animal(std::string theName);//类Animal的构造器
void eat();
void sleep();
void drool();//流鼻涕
protected:
std::string name;
};
Animal::Animal(std::string theName)//类Animal构造器函数
{
name = theName;
}
class Pig:public Animal//类Pig继承于类Animal,子类
{
public:
Pig(std::string theName);//类Pig的构造器
void climb();
void eat(int eatCount);//重载
};
Pig::Pig(std::string theName):Animal(theName)//类Pig的构造函数继承于类Animal的构造函数
{
}
class Turtle:public Animal//类Turtle继承于类Animal
{
public:
Turtle(std::string theName);//类Pig的构造器
void swim();
};
Turtle::Turtle(std::string theName):Animal(theName)//类Turtle的构造函数继承于类Animal的构造函数参数
{
}
void Animal::eat()//Animal类中的方法函数
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的会流口水 流口水。。。" << std::endl;
}
void Pig::climb()//类Pig中的climb方法函数
{
std::cout << "我是一只漂亮的小母猪猪,我会上树,邱。。。" << std::endl;
}
void Pig::eat(int eatCount)
{
std::cout << "我吃了" << eatCount << "碗馄饨!\n";
}
void Turtle::swim()//类Turtle中的swim方法函数
{
std::cout << "我是一只小甲鱼,当母猪抓我,我就游到海里,哈。。。" << std::endl;
}
int main()
{
Pig pig("小猪猪");//定义一个对象pig,由于其继承于类Animal,
//且构造函数也继承于类Animal的构造函数,所以参数"小猪猪"将赋值给类pig中的名字属性name
Turtle turtle("小甲鱼");//定义一个对象turtle
//pig.name = "小甲鱼"; //由于name被保护起来,所以赋值将报错
// std::cout << "这只猪的名字是:" << pig.name << std::endl;
// std::cout << "每只乌龟都有个伟大的名字:" << turtle.name << std::endl;
//pig.eat(); 由于eat在子类中被重载,所以不能再被调用;如果在基类重载则可以调用
turtle.eat();
pig.eat(15);//重载函数
pig.climb();
turtle.swim();
return 0;
}
第二十讲:一种特殊的友情关系——友元关系
友元关系是类之间的一种特殊关系,这种关系不仅允许友元类访问对方的public方法和属性,还允许友元访问对方的protected和private方法和属性。
实例1:友元关系访问保护量
#include <iostream>
#include <string>
using namespace std;
class Lovers//爱人关系,基类
{
public:
Lovers(string theName);//
void kiss(Lovers *lover);
void ask(Lovers *lover, string something);
protected:
string name;
friend class Others; //祸根,交友不慎。。。。 (友元关系),则Others可以访问name
};
class Boyfriend:public Lovers//Boyfriend子类,继承Lovers基类
{
public:
Boyfriend(string theName);
};
class Girlfriend:public Lovers//girlfriend子类,继承Lovers基类
{
public:
Girlfriend(string theName);
};
class Others//Others类 ,并没继承Lovers基类
{
public:
Others(string theName);
void kiss(Lovers *lover);
protected:
string name;
};
Lovers::Lovers(string theName)//Lovers类的构造函数
{
name = theName;
}
void Lovers::kiss(Lovers *lover)//Lovers *lover表示定义Lovers类的一个对象*lover
{
cout << name << "亲亲我们家的" << lover->name << endl;
}
void Lovers::ask(Lovers *lover,string something)
{
cout << "宝贝儿" << lover->name << "帮我" << something << endl;
}
Boyfriend::Boyfriend(string theName):Lovers(theName)
{
}
Girlfriend::Girlfriend(string theName):Lovers(theName)
{
}
Others::Others(string theName)
{
name = theName;
}
void Others::kiss(Lovers *lover)
{
cout << name << "亲一下" << lover->name << endl;
}
int main()
{
Boyfriend boyfriend("A君");
Girlfriend girlfriend("B妞");
Others others("路人甲");
girlfriend.kiss(&boyfriend);
girlfriend.ask(&boyfriend,"洗衣服啦");
cout << "\n当当当当!传说中的路人甲登场啦。。。。\n";
others.kiss(&girlfriend);
return 0;
}
第二十一讲:静态属性和静态方法
面对对象编程技术的一个重要特征是用一个对象把数据和对数据处理的方法封装在一起。
在前面的例子里,我们一直是在使用对象(也可以说某个类的实例)来调用方法,每个方法只处理调用它的那个对象所包含的数据,所有的数据都属于同一个对象。
C++允许我们把一个或多个成员声明为属于某个类,而不是仅属于该类的对象。(就是说这个类仅能让该类强暴)。好处是,程序员可以在没有创建任何对象的情况下调用有关的方法。另一个好处是能够让有关的数据仍在该类的所有对象间共享。
实例1:静态变量实例
#include <iostream>
#include <string>
using namespace std;
class Pet//定义Pet类
{
public:
Pet(string theName);//构造器,对count进行写操作
~Pet();//析构器,对count进行写操作
static int getCount();//定义静态函数,对count进行读操作
protected://不允许没有继承Pet类的其它类调用
string name;
private://定义的变量和函数只服务于Pet类中的函数
static int count;//定义静态变量(属性)
};
int Pet::count = 0;//注意这一句,他起码做了两件事(为count分配静态内存,初始化为0)
Pet::Pet(string theName)//构造函数
{
name = theName;
count++;
cout << "一只宠物出生了,名字叫做:" << name << "\n";
}
Pet::~Pet()//析构函数
{
count--;
cout << "\n" << name << "挂掉了\n";
}
int Pet::getCount()//返回count值
{
return count;
}
class Dog:public Pet//定义Dog类继承于Pet类
{
public:
Dog(string theName);
};
Dog::Dog(string theName):Pet(theName)//Dog的构造函数继承于Pet类中的Pet(theName)函数,即该构造函数调用时将调用Pet函数
{
}
class Cat:public Pet//定义Cat类继承于Pet类
{
public:
Cat(string theName);
};
Cat::Cat(string theName):Pet(theName)//Cat的构造函数继承于Pet类中的Pet(theName)函数
{
}
int main()
{
Dog dog("Tom");
Cat cat("Jerry");
cout << "\n已经诞生了" << Pet::getCount() << "只宠物!\n\n";
//如果有括号的话,编译器是在括号结束时调用dog_2与cat_2的析构函数,而没有的话,则是程序结尾处调用析构函数
// {
Dog dog_2("Tom_2");
Cat cat_2("Jerry_2");
cout << "\n现在呢,已经诞生了" << Pet::getCount() << "只宠物!";
// }
cout << "\n现在还剩下" << Pet::getCount() << "只宠物!";
return 0;
}
第二十二讲:静态属性和静态方法2
规则:
静态成员是所有对象共享的,所以不能在静态方法里访问非静态的元素。(因为每个对象都有自己的this指针,静态方法不是属于某个特定的对象,而是由全体对象共享的,静态成员存储地址与对象并不在一起)
非静态方法可以访问类的静态成员,也可以访问类的非静态成员。
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象的地址。
当一个对象被创建时,该对象的this指针就自动指向对象数据的首地址。
当我们调用一个方法时,this指针都会随着你提供的输入参数被秘密的传递给那个方法。
实例1:this指针应用
#include <iostream>
#include <string>
using namespace std;
class Pet//定义Pet类
{
public:
Pet(string theName);//构造器,对count进行写操作
~Pet();//析构器,对count进行写操作
static int getCount();//定义静态函数,对count进行读操作
protected://不允许没有继承Pet类的其它类调用
string name;
private://定义的变量和函数只服务于Pet类中的函数
static int count;//定义静态变量(属性)
};
int Pet::count = 0;//注意这一句,他起码做了两件事(为count分配静态内存,初始化为0) 外部对静态属性进行声明
Pet::Pet(string theName)//构造函数
{
name = theName;
count++;
cout << "一只宠物出生了,名字叫做:" << name << "\n";
}
Pet::~Pet()//析构函数
{
count--;
cout << "\n" << name << "挂掉了\n";
}
int Pet::getCount()//返回count值
{
return count;
}
class Dog:public Pet//定义Dog类继承于Pet类
{
public:
Dog(string theName);
};
Dog::Dog(string theName):Pet(theName)//Dog的构造函数继承于Pet类中的Pet(theName)函数,即该构造函数调用时将调用Pet函数
{
cout << "this:" << this << "\n";//打印this指针地址
}
class Cat:public Pet//定义Cat类继承于Pet类
{
public:
Cat(string theName);
};
Cat::Cat(string theName):Pet(theName)//Cat的构造函数继承于Pet类中的Pet(theName)函数
{
}
int main()
{
Dog dog("Tom");
Cat cat("Jerry");
cout << "dog:" << &dog << "\n";//打印指向dog对象的this指针地址
cout << "\n已经诞生了" << Pet::getCount() << "只宠物!\n\n";
//如果有括号的话,编译器是在括号结束时调用dog_2与cat_2的析构函数,而没有的话,则是程序结尾处调用析构函数
// {
Dog dog_2("Tom_2");
Cat cat_2("Jerry_2");
cout << "\n现在呢,已经诞生了" << Pet::getCount() << "只宠物!";
// }
cout << "\n现在还剩下" << Pet::getCount() << "只宠物!";
return 0;
}
第二十三讲:虚方法
TIPS:
引发问题的源头是我们使用了new在程序运行的时候才为dog和cat分配Dog类型和Cat类型的指针。这些都是它们在运行时才分配的类型,和它们在编译时的类型是不一样的。
如果拿不准要不要把某种方法声明为虚方法,那么就把它声明为虚方法好了。
在基类里把所有的方法都声明为虚方法会让最终的可执行代码的速度变得稍微慢一些,但好处是可以一劳永逸地确保程序的行为符合你的预期。
实例:虚方法的运用
//int *pointer = new int;//声明一个整型指针,并在程序运行时将它指向分配的内存空间
//delect *pointer;//删除指针,释放内存
#include <iostream>
#include <string>
using namespace std;
class Pet//定义Pet类
{
public:
Pet(string theName);//构造器,对count进行写操作
void eat();
void sleep();
virtual void play();//定义虚方法play,将在编译时为其分配内存
protected://不允许没有继承Pet类的其它类调用
string name;
};
Pet::Pet(string theName)
{
name = theName;
}
void Pet::eat()
{
cout << name << "正在吃东西!\n";
}
void Pet::sleep()
{
cout << name << "正在睡大觉!\n";
}
void Pet::play()
{
cout << name << "正在玩儿!\n";
}
class Cat:public Pet//定义Cat类继承于Pet类
{
public:
Cat(string theName);//构造器
void climb();
void play();
};
Cat::Cat(string theName):Pet(theName)//Cat的构造函数继承于Pet类中的Pet(theName)函数
{
}
void Cat::climb()
{
cout << name << "正在爬树!\n";
}
void Cat::play()
{
cout << name << "玩毛绒球!\n";
}
class Dog:public Pet//定义Dog类继承于Pet类
{
public:
Dog(string theName);//构造器
void bark();
void play();
};
Dog::Dog(string theName):Pet(theName)//Dog的构造函数继承于Pet类中的Pet(theName)函数
{
}
void Dog::bark()
{
cout << name << "汪汪汪!\n";
}
void Dog::play()
{
Pet::play();
cout << name << "正在追赶那只该死的猫!\n";
}
int main()
{
Pet *cat = new Cat("加菲");
Pet *dog = new Dog("欧迪");
cat -> sleep();
cat -> eat();
cat -> play();
dog -> sleep();
dog -> eat();
dog -> play();
delete cat;
delete dog;
return 0;
}
第二十四讲:抽象方法抽象方法(abstract method,也可以成为纯虚函数)
实例1:抽象方法应用
//int *pointer = new int;//声明一个整型指针,并在程序运行时将它指向分配的内存空间
//delect *pointer;//删除指针,释放内存
#include <iostream>
#include <string>
using namespace std;
class Pet//定义Pet类
{
public:
Pet(string theName);//构造器,对count进行写操作
virtual void eat();
virtual void sleep();
virtual void play() = 0;//定义抽象方法play,具体只需在继承类中声明实现即可,基类中不需要具体实现
protected://不允许没有继承Pet类的其它类调用
string name;
};
Pet::Pet(string theName)
{
name = theName;
}
void Pet::eat()
{
cout << name << "正在吃东西!\n";
}
void Pet::sleep()
{
cout << name << "正在睡大觉!\n";
}
class Cat:public Pet//定义Cat类继承于Pet类
{
public:
Cat(string theName);//构造器
void climb();
void play();
};
Cat::Cat(string theName):Pet(theName)//Cat的构造函数继承于Pet类中的Pet(theName)函数
{
}
void Cat::climb()
{
cout << name << "正在爬树!\n";
}
void Cat::play()
{
cout << name << "玩毛绒球!\n";
}
class Dog:public Pet//定义Dog类继承于Pet类
{
public:
Dog(string theName);//构造器
void bark();
void play();
};
Dog::Dog(string theName):Pet(theName)//Dog的构造函数继承于Pet类中的Pet(theName)函数
{
}
void Dog::bark()
{
cout << name << "汪汪汪!\n";
}
void Dog::play()
{
//Pet::play();
cout << name << "正在追赶那只该死的猫!\n";
}
int main()
{
Pet *cat = new Cat("加菲");
Pet *dog = new Dog("欧迪");
cat -> sleep();
cat -> eat();
cat -> play();
dog -> sleep();
dog -> eat();
dog -> play();
delete cat;
delete dog;
return 0;
}
多态性:是指用一个名字定义不同的函数,调用同一个名字的函数,却执行不同的操作,从而实现传说中的“一个接口,多种方法”。
一般类的析构函数都是释放内存资源,如果析构函数不被调用的话将会造成内存泄漏。
析构器定义为虚方法是为了当一个基类的指针删除一个派生类的对象时,派生类的析构函数可以被正确调用。
当类里面有虚函数时,编译器会给类添加一个虚函数表,里面存放着虚函数指针。为了节省资源,只有当一个类被用来作为基类时,我们才把析构函数写成虚函数。
实例2:析构函数解析
#include <iostream>
//析构器都是虚方法
using namespace std;
class ClxBase//定义基类ClxBase
{
public:
ClxBase()//构造器
{
cout << "ClxBase begin!\n";
};
virtual ~ClxBase()//析构器 ,如果其不为虚函数,则子类的析构函数将不被执行,将造成内存泄漏
{
cout << "ClxBase end!\n";
};
virtual void doSomething()//定义抽象方法doSomething 具体只需在继承类中声明实现即可(便可覆盖基类中定义),基类中可以不具体实现
{
cout << "Do something in class ClxBase!\n";
}
};
class ClxDerived:public ClxBase//定义子类ClxDerived继承于基类ClxBase
{
public:
ClxDerived()//子类构造器
{
cout << "ClxDerived begin!\n";
};
~ClxDerived()//子类析构器
{
cout << "Output from the destructor of class ClxDerived!\n";
};
void doSomething()
{
cout << "Do something in class ClxDerived!\n";
};
};
int main()
{
ClxBase *pTest = new ClxDerived;//定义一个pTest指针指向继承于ClxBase的ClxDerived对象,并为此指针分配内存
pTest -> doSomething();
delete pTest;
return 0;
}
第二十五讲:运算符的重载
所谓重载,就是重新赋予新的含义,函数重载是对一个已有的函数赋予新的含义。
运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该运算符。也就是说,运算符重载是通过定义函数实现的,运算符重载实质是是函数的重载。
实例1:复数加法
#include <iostream>
using namespace std;
class Complex//定义基类
{
public:
Complex();//构造器
Complex(double r,double i);//重载函数,输入参数不同,功能不同
Complex Complex_add(Complex &d);//加入一个方法,实现复数加法,Complex指其类型
void print();
private:
double real;
double imag;
};
Complex::Complex()//构造函数实现
{
real = 0;
imag = 0;
}
Complex::Complex(double r,double i)//重载函数实现
{
real = r;
imag = i;
}
Complex Complex::Complex_add(Complex &d)//Complex &d为定义基类对象d(被加数) complex_add为基类Complex方法
{ //第二个Complex为继承基类的加数对象
Complex c;//定义基类对象c //第一个Complex为其类型
c.real = real + d.real;
c.imag = imag + d.imag;
return c;
}
void Complex::print()
{
cout << "(" << real << ", " << imag << "i)\n";
}
int main()
{
Complex c1(3,4),c2(5,-10),c3;//定义三个对象c1、c2、c3及其重载函数参数值
c3 = c1.Complex_add(c2);
cout << "c1 = ";
c1.print();
cout << "c2 = ";
c2.print();
cout << "c1 + c2 = ";
c3.print();
return 0;
}
实例2:复数加法2
#include <iostream>
using namespace std;
class Complex//定义基类
{
public:
Complex();//构造器
Complex(double r,double i);//重载函数,输入参数不同,功能不同
friend Complex operator+(Complex &c,Complex &d);//加入一个方法,实现复数加法,Complex指其类型
void print();
private:
double real;
double imag;
};
Complex::Complex()//构造函数实现
{
real = 0;
imag = 0;
}
Complex::Complex(double r,double i)//重载函数实现
{
real = r;
imag = i;
}
//注意:这里作为友元函数,不属于Complex,记得别写::
Complex operator+(Complex &c,Complex &d)//Complex &d为定义基类对象d(被加数) complex_add为基类Complex方法
{
return Complex(c.real + d.real,c.imag + d.imag);
}
void Complex::print()
{
cout << "(" << real << ", " << imag << "i)\n";
}
int main()
{
Complex c1(3,4),c2(5,-10),c3;//定义三个对象c1、c2、c3及其重载函数参数值
c3 = operator+(c1,c2);
cout << "c1 = ";
c1.print();
cout << "c2 = ";
c2.print();
cout << "c1 + c2 = ";
c3.print();
return 0;
}
作业:分数加减乘除的操作符重载
#include <iostream>
#include <string>
#include <math.h>
using namespace std;
class Rational//定义基类
{
public:
Rational(int num,int denom); //构造器 num = 分子,denom = 分母
Rational operator+(Rational rhs);// rhs == right hand side(右手边参数)
Rational operator-(Rational rhs);
Rational operator*(Rational rhs);
Rational operator/(Rational rhs);
void print();//打印
private:
void normalize();//负责对分数的简化处理
int numerator; //分子
int denominator; //分母
};
Rational::Rational(int num,int denom)//构造函数实现
{
numerator = num;
denominator = denom;
normalize();
}
//normalize()对分数进行简化操作包括:
//1.只允许分子为负数,如果分母为负数则把负数挪到分子部分,如1/-2==-1/2
//2.利用欧几里德算法(辗转求余原理)将分数进行简化:2/10 => 1/5
void Rational::normalize()
{
//确保分母为正
if( denominator < 0)
{
numerator = -numerator;
denominator = -denominator;
}
//欧几里德算法
int a = abs(numerator);
int b = abs(denominator);
//求出最大公约数
while(b>0)
{
int t = a % b;//取余
a = b;
b = t;
}
//分子、分母分别除以最大公约数得到最简化分数
numerator /= a;
denominator /= a;
}
//a c a*d c*b a*d + c*d
//- + - = --- + --- = ----------
//b d b*d b*d b*d
Rational Rational::operator+(Rational rhs)//分数的加运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*b + c*d;
int f = b*d;
return Rational(e,f);
}
//a c a -c
//- - - = - + --
//b d b d
Rational Rational::operator-(Rational rhs)//分数的减运算
{
rhs.numerator = -rhs.numerator; //被减数分子取负数
return operator+(rhs);
}
//a c a*c
//- * - = ---
//b d b*d
Rational Rational::operator*(Rational rhs)//分数的乘运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*c;
int f = b*d;
return Rational(e,f);
}
//a c a d
//- / - = - * -
//b d b c
Rational Rational::operator/(Rational rhs)//分数的除运算
{
//rhs的分子分母互换
int t = rhs.numerator;
rhs.numerator = rhs.denominator;
rhs.denominator = t;
return operator*(rhs);
}
void Rational::print() //打印最简分数
{
if(numerator % denominator == 0)
cout << numerator / denominator;
else
cout << numerator << "/" << denominator;
}
int main()
{
Rational f1(2,16);//定义f1对象,且传入(2,16)参数
Rational f2(7,8);
//测试有理数加法运算
Rational res = f1 + f2;//相当于res=f1.operator+(f2)
f1.print();
cout << " + ";
f2.print();
cout << " = ";
res.print();
cout << "\n";
//测试有理数减法运算
res = f1 - f2;
f1.print();
cout << " - ";
f2.print();
cout << " = ";
res.print();
cout << "\n";
//测试有理数乘法运算
res = f1 * f2;
f1.print();
cout << " * ";
f2.print();
cout << " = ";
res.print();
cout << "\n";
//测试有理数除法运算
res = f1 / f2;
f1.print();
cout << " / ";
f2.print();
cout << " = ";
res.print();
cout << "\n";
return 0;
}
第二十六讲:运算符重载2
去重载一个操作符,应该只有在必要的时候,比如实现一种新的数据类型时,才重载操作符。
重载操作符的目的是为了让代码更容易阅读和理解。
第二十七讲:运算符左移<<重载
一般来说,在调用operator<<()重载函数时,传递给它的是哪一个流,它返回的就应该是那个流的一个引用。
实例:左移操作符重载
#include <iostream>
#include <string>
#include <math.h>
using namespace std;
class Rational//定义基类
{
public:
Rational(int num,int denom); //构造器 num = 分子,denom = 分母
Rational operator+(Rational rhs);// rhs == right hand side(右手边参数)
Rational operator-(Rational rhs);
Rational operator*(Rational rhs);
Rational operator/(Rational rhs);
private:
void normalize();//负责对分数的简化处理
int numerator; //分子
int denominator; //分母
friend ostream& operator<<(ostream& os,Rational f);//传递给它的是哪一个流,它返回的就是那个流的一个引用
};
Rational::Rational(int num,int denom)//构造函数实现
{
numerator = num;
denominator = denom;
normalize();
}
//normalize()对分数进行简化操作包括:
//1.只允许分子为负数,如果分母为负数则把负数挪到分子部分,如1/-2==-1/2
//2.利用欧几里德算法(辗转求余原理)将分数进行简化:2/10 => 1/5
void Rational::normalize()
{
//确保分母为正
if( denominator < 0)
{
numerator = -numerator;
denominator = -denominator;
}
//欧几里德算法
int a = abs(numerator);
int b = abs(denominator);
//求出最大公约数
while(b>0)
{
int t = a % b;//取余
a = b;
b = t;
}
//分子、分母分别除以最大公约数得到最简化分数
numerator /= a;
denominator /= a;
}
//a c a*d c*b a*d + c*d
//- + - = --- + --- = ----------
//b d b*d b*d b*d
Rational Rational::operator+(Rational rhs)//分数的加运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*b + c*d;
int f = b*d;
return Rational(e,f);
}
//a c a -c
//- - - = - + --
//b d b d
Rational Rational::operator-(Rational rhs)//分数的减运算
{
rhs.numerator = -rhs.numerator; //被减数分子取负数
return operator+(rhs);
}
//a c a*c
//- * - = ---
//b d b*d
Rational Rational::operator*(Rational rhs)//分数的乘运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*c;
int f = b*d;
return Rational(e,f);
}
//a c a d
//- / - = - * -
//b d b c
Rational Rational::operator/(Rational rhs)//分数的除运算
{
//rhs的分子分母互换
int t = rhs.numerator;
rhs.numerator = rhs.denominator;
rhs.denominator = t;
return operator*(rhs);
}
ostream& operator<<(ostream& os,Rational f);//函数声明
int main()
{
Rational f1(2,16);//定义f1对象,且传入(2,16)参数
Rational f2(7,8);
//测试有理数加法运算
cout << f1 << " + " << f2 << " == " << (f1+f2) << "\n"; // 左移操作符<<已经被重载了
//测试有理数减法运算
cout << f1 << " - " << f2 << " == " << (f1-f2) << "\n"; //<< f1中由于f1属于Rational类型,将自动转到打印分数形式输出
//测试有理数乘法运算
cout << f1 << " * " << f2 << " == " << (f1*f2) << "\n";//<< "+"中由于<<后面参数是字符串,所以使用系统默认的打印
//测试有理数除法运算
cout << f1 << " / " << f2 << " == " << (f1/f2) << "\n";
return 0;
}
ostream& operator<<(ostream& os,Rational f)//并不属于Rational类,是一个独立的函数
{
if(f.numerator % f.denominator == 0)
os << f.numerator / f.denominator;
else
os << f.numerator << "/" << f.denominator;//打印分数
return os;
}
第二十八讲:多继承(multiple inheritance)
多继承语法:
//助教类既继承于老师类,又继承于学生类
class Assistant:public Teacher,public Student
{
};
当遇到的问题无法只用一个“是一个”关系来描述的时候,就是多继承出场的时候。例即是学生,又是人,还是助教。
#include <iostream>
#include <string>
using namespace std;
#include <string>
//人类
class Person
{
public:
Person(string theName);//构造函数,theName为类的输入参数
void introduce();
protected:
string name;
};
Person::Person(string theName)//构造函数实现
{
name = theName;
}
void Person::introduce()//introduce()函数实现
{
cout << "大家好,我是" << name << "。\n\n";
}
//老师类继承于人类
class Teacher:public Person
{
public:
Teacher(string theName,string theClass);
void teach();//教书
void introduce();
protected:
string classes;
};
Teacher::Teacher(string theName,string theClass):Person(theName)//老师的名字继承于人类中的名字
{
classes = theClass;
}
void Teacher::teach()
{
cout<< name << " 教 "<< classes << "。\n\n";
}
void Teacher::introduce()
{
cout<<"大家好,我是 "<< name <<" ,我教 "<< classes << "。\n\n";
}
//学生类继承于人类
class Student:public Person
{
public:
Student(string theName,string theClass);
void attendClass();//要上课
void introduce();
protected:
string classes;
};
Student::Student(string theName,string theClass):Person(theName)//学生名字继承于人类中的名字
{
classes = theClass;
}
void Student::attendClass()
{
cout<< name <<"加入"<< classes << "学习。\n\n";
}
void Student::introduce()
{
cout<< "大家好,我是" << name << ",我在" << classes << "学习。\n\n";
}
//助教类既继承于老师类,又继承于学生类
class Assistant:public Teacher,public Student
{
public:
Assistant(string theName,string classTeaching,string classAttending);
void introduce();
};
Assistant::Assistant(string theName,string classTeaching,string classAttending):Teacher(theName, classTeaching),Student(theName,classAttending)
{
//多继承 助教既继承老师类,又继承学生类
}
void Assistant::introduce()
{
cout << "大家好,我是" << Student::name << ".我教" << Teacher::classes << ",";
cout << "同时我在" << Student::classes << "学习。\n\n";
}
int main()
{
Teacher teacher("小甲鱼","C++入门班");
Student student("迷途羔羊","C++入门班");
Assistant assistant("丁丁","C++入门班","C++进阶班");
teacher.introduce();
teacher.teach();
student.introduce();
student.attendClass();
assistant.introduce();
assistant.teach();
assistant.attendClass();
return 0;
}
第二十九讲:虚继承
通过虚继承某个基类,就是在告诉编译器:从当前这个类再派生出来的子类只能拥有那个基类的一个实例。
虚继承的语法:
class Teacher:virtual public Person
{
}
这样做的话便可以让Student和Teacher类都虚继承自Person类,编译器将确保从Student和Teacher类再派生出来的子类只能拥有一份Person类的属性。
实例:虚继承应用
#include <iostream>
#include <string>
//虚继承可以让Student和Teacher类都虚继承自Person类,
//编译器将确保从Student和Teacher类再派生出来的子类只能拥有一份Person类的属性。
using namespace std;
#include <string>
//人类
class Person
{
public:
Person(string theName);//构造函数,theName为类的输入参数
void introduce();
protected:
string name;
};
Person::Person(string theName)//构造函数实现
{
name = theName;
}
void Person::introduce()//introduce()函数实现
{
cout << "大家好,我是" << name << "。\n\n";
}
//老师类继承于人类
class Teacher:virtual public Person//虚继承,再有类继承Teacher类时,将只拥有一份Person类的属性。
{
public:
Teacher(string theName,string theClass);
void teach();//教书
void introduce();
protected:
string classes;
};
Teacher::Teacher(string theName,string theClass):Person(theName)//老师的名字继承于人类中的名字
{
classes = theClass;
}
void Teacher::teach()
{
cout<< name << " 教 "<< classes << "。\n\n";
}
void Teacher::introduce()
{
cout<<"大家好,我是 "<< name <<" ,我教 "<< classes << "。\n\n";
}
//学生类继承于人类
class Student:virtual public Person//虚继承,再有类继承Student类时,将只拥有一份Person类的属性
{
public:
Student(string theName,string theClass);
void attendClass();//要上课
void introduce();
protected:
string classes;
};
Student::Student(string theName,string theClass):Person(theName)//学生名字继承于人类中的名字
{
classes = theClass;
}
void Student::attendClass()
{
cout<< name <<"加入"<< classes << "学习。\n\n";
}
void Student::introduce()
{
cout<< "大家好,我是" << name << ",我在" << classes << "学习。\n\n";
}
//助教类既继承于老师类,又继承于学生类
class Assistant:public Teacher,public Student
{
public:
Assistant(string theName,string classTeaching,string classAttending);
void introduce();
};
Assistant::Assistant(string theName,string classTeaching,string classAttending)
:Teacher(theName, classTeaching),Student(theName,classAttending),Person(theName)
//由于虚继承,Teacher和Student都不能拥有Person类中的属性和方法
//只能由Person类自己给出
{
//多继承 助教既继承老师类,又继承学生类
}
void Assistant::introduce()
{
cout << "大家好,我是" << Student::name << ".我教" << Teacher::classes << ",";
cout << "同时我在" << Student::classes << "学习。\n\n";
}
int main()
{
Teacher teacher("小甲鱼","C++入门班");
Student student("迷途羔羊","C++入门班");
Assistant assistant("丁丁","C++入门班","C++进阶班");
teacher.introduce();
teacher.teach();
student.introduce();
student.attendClass();
assistant.introduce();
assistant.teach();
assistant.attendClass();
return 0;
}
第三十讲:错误处理和调试
程序出错可以分为两大类:编译时出错(complie-time error)和运行时错误(run-time error)
suggest1:培养并保持一种编程风格
suggest2:认真对待编译器给出的错误/警告信息
suggest3:写代码三思而后行(先画流程图)
suggest4:注意检查最基本的语法
suggest5:把可能有问题的代码行注释
suggest6:换一个环境或开发工具试试
suggest7:检查自己是否已经把所有必要的头文件全部include进来
suggest8:留意变量的作用域和命名空间
suggest9:休息一下
suggest10:使用调试工具
最后避免错误的另一个好方法就是把调试好的代码另外保存起来并不再改动它,然后把代码划分成各个模块,用它们来搭建新的应用程序。
运行时错误:
1、培养保持良好编程风格
2、多用注释,用好注释
3、注意操作符的优先级
4、不要忘记对用户输入和文件输入进行合理性检查
5、不要做任何假设,想当然
6、把程序划分成一些比较小的单元模块来测试
第三十一讲:错误处理与调试2
让函数返回错误代码
实例:范围限制
#include <iostream>
#include <climits>
using namespace std;
class Factorial
{
public:
Factorial(unsigned short num);
unsigned long getFactorial();
bool inRange();
private:
unsigned short num;
};
Factorial::Factorial(unsigned short num)
{
this->num = num;//传入的参数num值给了Factorial类中的属性num
}
unsigned long Factorial::getFactorial()
{
unsigned long sum = 1;
for(int i=1;i <= num; i++)
{
sum *= i;
}
return sum;
}
bool Factorial::inRange()
{
unsigned long max = ULONG_MAX;//ULONG_MAX在头文件climits中
for(int i = num; i >= 1; --i)//将最大值ULONG_MAX除以输入的num阶乘
{
max /= i;
}
if(max < 1)
return false;
else
return true;
}
int main()
{
unsigned short num = 0;
cout << "请输入一个整数:";
cin >> num;
Factorial fac(num);
if(fac.inRange())
{
cout << num << "的阶乘值是" << fac.getFactorial() << "\n\n";
}
else
{
cout << "您所输入的值太大!\n\n";
}
}
第三十二讲:assert函数和捕获异常
C语言和C++都有一个专门为调试而准备的工具函数---assert函数。这个函数是在assert.h库文件里定义的。
实例1:assert函数应用
#include <cassert>
//assert()函数需要有一个参数,它将测试这个输入参数的真或者假状态
//如果为真 Do nothing
//如果为假 Do something
int main()
{
int i = 20;
assert( i == 65);//如果括号内为真,则过;假则中断程序
return 0;
}
同样为了对付潜在的编程错误(尤其是在运行时的错误),捕获异常是一种完全不同的方法。简单说,异常就是与预期不相符合的反常现象。
注:每条try语句至少要有一条配对的catch语句。必须定义catch语句以便让它接收一个特定类型的参数。
当某个try语句块里执行过throw语句,它后面的所有语句(截止到这个try语句块末尾)将永远不会被执行。
如果try语句块无法找到一个与之匹配的catch语句块,它抛出的异常将中止程序的执行。
实例2:捕获异常(try,catch,throw)
#include <iostream>
#include <climits>
using namespace std;
unsigned long returnFactorial(unsigned short num) throw(const char*);
int main()
{
unsigned short num = 0;
cout << "请输入一个整数:";
while( !(cin>>num) || (num < 1) )
{
cin.clear();//清楚状态
cin.ignore(100,'\n'); //清除缓存区
cout << "请输入一个整数:";
}
cin.ignore(100,'\n');
try
{
unsigned long factorial = returnFactorial(num);//如果异常,则抛出字符型的*e异常
//直接跳转到catch继续执行
cout << num << "的阶乘值是:" << factorial;
}
catch(const char *e)//如果正常则不执行,抛出异常则执行
{
cout << "error";
}
return 0;
}
unsigned long returnFactorial(unsigned short num) throw(const char*)
{
unsigned long sum = 1;
unsigned long max = ULONG_MAX;
for(int i = 1;i <= num; i++)
{
sum *= i;
max /= i;
}
if(max < 1)
{
throw"悲催。。。该基数太大,无法在该计算机计算求出阶乘值。\n";
}
else
{
return sum;
}
}
第三十三讲:动态的内存管理
到目前为止,所讲解的每个示例程序在完成它的任务时所使用的内存空间都是固定不变的。不能在程序运行期间动态增加或减少内存空间(即静态内存)。
在很多时候,需要存储的数据量到底有多大在事先往往是一个未知数,想要处理好这类情况,就需要在C++程序里使用动态内存。
动态内存由一些没有名字、只有地址的内存块构成,那些内存块是在程序运行期间动态分配的。他们来自一个由标准C++库替你管理的大池子(内存池),从内存池申请一些内存需要用到new语句,它将根据你提供的数据类型分配一块大小适当的内存。
new语句返回的内存块很可能充满“垃圾”数据,所以我们通常先往里边写一些东西覆盖,再访问它们,或者在类直接写一个构造器来初始化。
在使用动态内存时,最重要的原则是每一条new语句都必须有一条与之配对的delete语句,否则将导致内存泄漏。
delete语句只释放给定指针变量正指向的内存块,不影响这个指针。在执行delete语句之后,那个内存块被释放了,但指针变量还依然健在。
实例:动态内存管理
#include <iostream>
#include <string>
using namespace std;
class Company
{
public:
Company(string theName);
virtual void printInfo();//需要打印的一些信息
protected:
string name;
};
Company::Company(string theName)//基类构造函数实现
{
name = theName;
}
void Company::printInfo()
{
cout << "这家公司的名字叫:" << name << "。\n";
}
class TechCompany:public Company
{
public:
TechCompany(string theName,string product);
virtual void printInfo();//定义为虚方法,为了使科技公司类中的打印函数能够覆盖基类的打印函数
private:
string product;
};
TechCompany::TechCompany(string theName,string product):Company(theName)//子类构造函数实现
{
this->product = product;
}
void TechCompany::printInfo()
{
cout << name << "公司大量生产了 " << product << "这款产品!\n";
}
int main()
{
Company *company = new Company("APPLE");//定义一个基类对象compnay,为其分配内存,指针指向其地址
company -> printInfo();//调用基类的打印方法
delete company;//删除指针地址
company = NULL;//为指针指向的地址赋空值
company = new TechCompany("APPLE","IPHONE");//指针指向新定义的子类TechCompany
company -> printInfo();//调用子类的打印方法
delete company;
company = NULL;
return 0;
}
第三十四讲:动态数组
虽然前面讲过的用new给基本类型和对象在运行时分配内存,但它们的尺寸在编译时就已经确定下来——因为我们为之申请内存的数据类型在程序中有明确的定义,有明确的单位长度。
但有些时候,必须等到程序运行时才能确定需要申请多少内存,甚至还需要根据程序的运行情况追加申请更多的内存。
例如: int *x = new int[10];//x表示整型数组的数组名
实例:动态数组
#include <iostream>
#include <string>
//让new函数申请内存并返回一个指向内存块的指针
using namespace std;
int main()
{
unsigned int count = 0;
cout << "请输入数组的元素个数:\n";
cin >> count;
int *x = new int[count];//在程序运行时才申请内存,内存块指针指向x
for(int i=0;i<count;i++)
{
x[i] = i;
}
for(int i=0;i<count;i++)
{
cout << "x[" <<i << "]的值是:" << x[i] << "\n";
}
return 0;
}
第三十五讲:从函数或方法返回内存
在函数里调用new语句为某种对象或某种基本数据类型分配一块内存,再把那块内存的地址返回给程序的主代码,主代码将使用那块内存并在完成有关操作后立刻释放。
实例1:函数或方法返回内存
#include <iostream>
using namespace std;
int *newInt(int value);//声明一个函数,由int *知其返回值为指向整型的一个地址
int main()
{
int *x = newInt(20);//定义指针类型x
cout << *x;
delete x;//释放地址
x = NULL;//指针填充NULL
return 0;
}
int *newInt(int value)
{
int *myInt = new int;//申请一个整型内存块(4个字节),并将指针(myInt)指向内存块的地址
*myInt = value;//解引用,赋值
return myInt;
}
函数指针:指向函数首地址的指针变量称为函数指针。
#include <stdio.h>
int fun(int x, int y);
int main()
{
int i,a,b;
int (*p)(); //声明函数指针
p = fun; //给函数指针p赋值,使它指向函数fun
printf("请输十个数字:\n");
for(i=0;i<10;i++)
{
scanf("%d",&b);
a = (*p)(a,b); //通过指针p调用函数fun
}
printf("The Max Number is:%d",a);
return 0;
}
fun(int x,int y)
{
int z;
z = (x>y)?x:y;
return(z);
}
指针函数:一个函数可以带回一个整型数据的值,字符类型值和实型类型的值,还可以带回指针类型的数据,使其指向某个地址单元。
实例2:函数指针
#include <iostream>
using namespace std;
int *newInt(int value);//声明一个指针函数,其返回值为一个地址
int main()
{
int *x = newInt(20);//新建一个整型变量空间,并将x指针指向这个空间
cout << *x;
delete x;
x = NULL;
return 0;
}
int *newInt(int value)//指针函数的具体实现
{
int *myInt = new int;
*myInt = value;
return myInt;
}
第三十六讲:副本构造器
我们可以把一个对象赋值给一个类型与之相同的变量,编译器将生成必要的代码把“源”对象各属性的值分别赋值给“目标”对象的对应成员。这种赋值行为称之为逐位复制。(但如果某些成员变量是指针的话,对象成员逐位复制的结果是你将拥有两个一模一样的实例,而这两个副本里的同名指针会指向相同的地址)
实例1:带指针变量的等号重载
#include <iostream>
#include <string>
using namespace std;
class MyClass
{
public:
MyClass(int *p);//构造器
~MyClass();//析构器
MyClass &operator = (const MyClass &rhs);//运算符(=)重载
void print();//打印
private:
int *ptr;
};
MyClass::MyClass(int *p)//构造器实现
{
ptr = p;
}
MyClass::~MyClass()//析构器实现
{
delete ptr;
}
// a = b; 如obj1 = obj2;
MyClass &MyClass::operator=(const MyClass &rhs)//运算符(=)重载实现,rhs为另一对象
{
if(this != &rhs)//obj1不等于obj2时,this指针指的是指向当前类生成的对象(此处指第一个MyClass)
{
delete ptr;//删除地址ptr,释放obj1的内存
ptr = new int;//创建新的内存给指针ptr
*ptr = *rhs.ptr;//解引用,将obj2的值赋值给obj1
//cout << "复制指针!\n";
}
else
{
cout << "赋值号两边为同个对象,不做处理!\n";//obj1 = obj2时
}
return *this;//返回第一个MyClass对象
}
void MyClass::print()//打印*ptr的值
{
cout << *ptr << endl;
}
int main()
{
MyClass obj1(new int(1));//创建对象obj1
MyClass obj2(new int(2));//创建对象obj2
obj1.print();//打印对象obj1中指针指向的地址的储存值
obj2.print();//打印对象obj2中指针指向的地址的储存值
obj2 = obj1;//obj1通过等号重载给obj2
obj1.print();//打印对象obj1中指针指向的地址的储存值
obj2.print();//打印对象obj2中指针指向的地址的储存值
return 0;
}
实例2:副本构造器
#include <iostream>
#include <string>
using namespace std;
class MyClass
{
public:
MyClass(int *p);//主构造器
MyClass(const MyClass &rhs);//副本构造器
~MyClass();//析构器
MyClass &operator = (const MyClass &rhs);//运算符(=)重载,对象复制,括号里声明对象调用副本构造器
void print();//打印
private:
int *ptr;
};
MyClass::MyClass(int *p)//主构造器实现
{
cout << "进入主构造器\n";
ptr = p;
cout << "离开主构造器\n";
}
MyClass::MyClass(const MyClass &rhs)//副本构造器实现
{
cout << "进入副本构造器\n";
*this = rhs; //等号赋值重载
cout << "离开副本构造器\n";
}
MyClass::~MyClass()//析构器实现
{
cout << "进入析构器\n";
delete ptr;
cout << "离开析构器\n";
}
// a = b; 如obj1 = obj2;
MyClass &MyClass::operator=(const MyClass &rhs)//运算符(=)重载实现,rhs为另一对象
{
cout << "进入赋值语句重载\n";
if(this != &rhs)//obj1不等于obj2时,this指针指的是指向当前类生成的对象(此处指第一个MyClass)
{
delete ptr;//删除地址ptr,释放obj1的内存
ptr = new int;//创建新的内存给指针ptr
*ptr = *rhs.ptr;//解引用,将obj2的值赋值给obj1
//cout << "复制指针!\n";
}
else//obj1与obj2为同一个对象时
{
cout << "赋值号两边为同个对象,不做处理!\n";
}
cout << "离开赋值语句重载\n";
return *this;//返回第一个MyClass对象
}
void MyClass::print()//打印*ptr的值
{
cout << *ptr << endl;
}
int main()
{
MyClass obj1(new int(1));//创建对象obj1
MyClass obj2(new int(2));//创建对象obj2
obj2 = obj1;//obj1通过等号重载给obj2
obj1.print();//打印对象obj1中指针指向的地址的储存值
obj2.print();//打印对象obj2中指针指向的地址的储存值
cout << "---------------------------------------------\n";
MyClass obj3(new int(3));//创建对象obj3
MyClass obj4 = obj3;//副本构造器
obj3.print();//打印对象obj3中指针指向的地址的储存值
obj4.print();//打印对象obj4中指针指向的地址的储存值
cout << "---------------------------------------------\n";
MyClass obj5(new int(5));//创建对象obj5
obj5 = obj5;
obj5.print();//打印对象obj5中指针指向的地址的储存值
return 0;
}
第三十七讲:高级强制类型转换
实例1:静态强制转换
#include <iostream>
#include <string>
using namespace std;
class Company
{
public:
Company(string theName,string product);
virtual void printInfo();
protected:
string name;
string product;
};
Company::Company(string theName,string product)
{
name = theName;
this->product = product;
}
void Company::printInfo()
{
cout << "这个公司的名字叫:" << name << "正在生产" << product << "\n";
}
class TechCompany:public Company
{
public:
TechCompany(string theName,string product);
virtual void printInfo();
};
TechCompany::TechCompany(string theName,string product):Company(theName,product)
{
}
void TechCompany::printInfo()
{
cout << name << "公司大量生产了 " << product << "这款产品!\n";
}
int main()
{
Company *company = new TechCompany("APPLE","Iphone");//定义TechCompany对象,Company类型
//指针company指向其地址
TechCompany *techCompany = (TechCompany*)company; //将Company类型强制转换为TechCompany类型
techCompany->printInfo();
delete company;//释放内存(此处company与techCompany两个指针都指向TechCompany定义的对象)
//所以释放内存只需要释放一次即可
company = NULL;
techCompany = NULL;
return 0;
}
实例2:动态强制转换
#include <iostream>
#include <string>
using namespace std;
class Company
{
public:
Company(string theName,string product);
virtual void printInfo();
protected:
string name;
string product;
};
Company::Company(string theName,string product)
{
name = theName;
this->product = product;
}
void Company::printInfo()
{
cout << "这个公司的名字叫:" << name << "正在生产" << product << "\n";
}
class TechCompany:public Company
{
public:
TechCompany(string theName,string product);
virtual void printInfo();
};
TechCompany::TechCompany(string theName,string product):Company(theName,product)
{
}
void TechCompany::printInfo()
{
cout << name << "公司大量生产了 " << product << "这款产品!\n";
}
int main()
{
Company *company = new Company("APPLE","Iphone");//定义Company对象,
//指针company类型为Company,指向其地址
TechCompany *techCompany = dynamic_cast<TechCompany*>(company); //将Company类型强制转换为TechCompany类型
if(techCompany != NULL)
{
cout << "成功!\n";
}
else
{
cout << "悲催!\n";
}
delete company;//释放内存(此处company与techCompany两个指针都指向TechCompany定义的对象)
//所以释放内存只需要释放一次即可
company = NULL;
techCompany = NULL;
return 0;
}
第三十八讲:避免内存泄漏
如果程序会运行很长时间(例如在服务器上,注意不是所有的操作系统都像windows一样每天都要重启),并且在不停地申请新的内存块,忘记释放那些已经不再有用的老内存迟早会把内存消耗殆尽,直接导致后边的new操作无法执行甚至崩溃。
动态内存是没有作用域的,所以必须由程序员来跟踪它们的使用情况,并在不需要用到它们的时候及时把它们归还给系统。虽然动态分配的内存块没有作用域,但用来保存其地址的指针变量是受作用域影响的。
第三十九讲:命名空间和模块化编程
模块化:把程序划分成多个组成部分(即所谓的模块)。这是通过把程序代码分散到多个文件里,等编译程序时再把那些文件重新组合在一起实现的。
C++预处理器的#include指令提供了一种能够让编译器在编译主程序时把其他文件的内容包括进来的机制。例如用这个指令来包括iostream头文件。
实例:头文件应用
Rational.h文件
//Ration.h
//Create by 亦我飞也
//这个头文件用来声明有理数类(Rational class)
//类里面对四则运算进行重载,以实现分数运算
#include <iostream>
class Rational//定义基类
{
public:
Rational(int num,int denom); //构造器 num = 分子,denom = 分母
Rational operator+(Rational rhs);// rhs == right hand side(右手边参数)
Rational operator-(Rational rhs);
Rational operator*(Rational rhs);
Rational operator/(Rational rhs);
private:
void normalize();//负责对分数的简化处理
int numerator; //分子
int denominator; //分母
friend std::ostream& operator<<(std::ostream& os,Rational f);//传递给它的是哪一个流,它返回的就是那个流的一个引用
};
Rational.cpp文件
#include <iostream>
#include <string>
#include <math.h>
#include "Rational.h"//系统级别用单尖括号,自定义级别用双引号
using namespace std;
//class Rational//定义基类
//{
//public:
// Rational(int num,int denom); //构造器 num = 分子,denom = 分母
//
// Rational operator+(Rational rhs);// rhs == right hand side(右手边参数)
// Rational operator-(Rational rhs);
// Rational operator*(Rational rhs);
// Rational operator/(Rational rhs);
//
//private:
// void normalize();//负责对分数的简化处理
//
// int numerator; //分子
// int denominator; //分母
//
// friend ostream& operator<<(ostream& os,Rational f);//传递给它的是哪一个流,它返回的就是那个流的一个引用
//};
Rational::Rational(int num,int denom)//构造函数实现
{
numerator = num;
denominator = denom;
normalize();
}
//normalize()对分数进行简化操作包括:
//1.只允许分子为负数,如果分母为负数则把负数挪到分子部分,如1/-2==-1/2
//2.利用欧几里德算法(辗转求余原理)将分数进行简化:2/10 => 1/5
void Rational::normalize()
{
//确保分母为正
if( denominator < 0)
{
numerator = -numerator;
denominator = -denominator;
}
//欧几里德算法
int a = abs(numerator);
int b = abs(denominator);
//求出最大公约数
while(b>0)
{
int t = a % b;//取余
a = b;
b = t;
}
//分子、分母分别除以最大公约数得到最简化分数
numerator /= a;
denominator /= a;
}
//a c a*d c*b a*d + c*d
//- + - = --- + --- = ----------
//b d b*d b*d b*d
Rational Rational::operator+(Rational rhs)//分数的加运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*b + c*d;
int f = b*d;
return Rational(e,f);
}
//a c a -c
//- - - = - + --
//b d b d
Rational Rational::operator-(Rational rhs)//分数的减运算
{
rhs.numerator = -rhs.numerator; //被减数分子取负数
return operator+(rhs);
}
//a c a*c
//- * - = ---
//b d b*d
Rational Rational::operator*(Rational rhs)//分数的乘运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*c;
int f = b*d;
return Rational(e,f);
}
//a c a d
//- / - = - * -
//b d b c
Rational Rational::operator/(Rational rhs)//分数的除运算
{
//rhs的分子分母互换
int t = rhs.numerator;
rhs.numerator = rhs.denominator;
rhs.denominator = t;
return operator*(rhs);
}
ostream& operator<<(ostream& os,Rational f);//函数声明
int main()
{
Rational f1(2,16);//定义f1对象,且传入(2,16)参数
Rational f2(7,8);
//测试有理数加法运算
cout << f1 << " + " << f2 << " == " << (f1+f2) << "\n"; // 左移操作符<<已经被重载了
//测试有理数减法运算
cout << f1 << " - " << f2 << " == " << (f1-f2) << "\n"; //<< f1中由于f1属于Rational类型,将自动转到打印分数形式输出
//测试有理数乘法运算
cout << f1 << " * " << f2 << " == " << (f1*f2) << "\n";//<< "+"中由于<<后面参数是字符串,所以使用系统默认的打印
//测试有理数除法运算
cout << f1 << " / " << f2 << " == " << (f1/f2) << "\n";
return 0;
}
ostream& operator<<(ostream& os,Rational f)//并不属于Rational类,是一个独立的函数
{
if(f.numerator % f.denominator == 0)
os << f.numerator / f.denominator;
else
os << f.numerator << "/" << f.denominator;//打印分数
return os;
}
第四十讲:命名空间与模块化编程2
利用C++预处理器,我们可以让头文件只在这个类还没有被声明过的情况下才声明它。作为一种固定模式,这里使用常量名通常与相应的文件名保持一致(换成大写),把句号替换为下划线。例如rational.h文件对应RATIONAL_H
Rational.h文件
//Ration.h
//Create by 亦我飞也
//这个头文件用来声明有理数类(Rational class)
//类里面对四则运算进行重载,以实现分数运算
//如果RATIONAL_H未被定义,则定义rational.h,执行定义类程序
//如果RATIONAL_H已经被定义,程序则直接跳到endif(即定义类程序只会执行一次)
#ifndef RATIONAL_H//如果没有定义rational.h
#define RATIONAL_H//定义rational.h
#include <iostream>
class Rational//定义基类
{
public:
Rational(int num,int denom); //构造器 num = 分子,denom = 分母
Rational operator+(Rational rhs);// rhs == right hand side(右手边参数)
Rational operator-(Rational rhs);
Rational operator*(Rational rhs);
Rational operator/(Rational rhs);
private:
void normalize();//负责对分数的简化处理
int numerator; //分子
int denominator; //分母
friend std::ostream& operator<<(std::ostream& os,Rational f);//传递给它的是哪一个流,它返回的就是那个流的一个引用
};
#endif//如果已经定义rational.h,则结束
Rational.cpp文件
#include <iostream>
#include <string>
#include <math.h>
#include "Rational.h"//系统级别用单尖括号,自定义级别用双引号
using namespace std;
Rational::Rational(int num,int denom)//构造函数实现
{
numerator = num;
denominator = denom;
normalize();
}
//normalize()对分数进行简化操作包括:
//1.只允许分子为负数,如果分母为负数则把负数挪到分子部分,如1/-2==-1/2
//2.利用欧几里德算法(辗转求余原理)将分数进行简化:2/10 => 1/5
void Rational::normalize()
{
//确保分母为正
if( denominator < 0)
{
numerator = -numerator;
denominator = -denominator;
}
//欧几里德算法
int a = abs(numerator);
int b = abs(denominator);
//求出最大公约数
while(b>0)
{
int t = a % b;//取余
a = b;
b = t;
}
//分子、分母分别除以最大公约数得到最简化分数
numerator /= a;
denominator /= a;
}
//a c a*d c*b a*d + c*d
//- + - = --- + --- = ----------
//b d b*d b*d b*d
Rational Rational::operator+(Rational rhs)//分数的加运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*b + c*d;
int f = b*d;
return Rational(e,f);
}
//a c a -c
//- - - = - + --
//b d b d
Rational Rational::operator-(Rational rhs)//分数的减运算
{
rhs.numerator = -rhs.numerator; //被减数分子取负数
return operator+(rhs);
}
//a c a*c
//- * - = ---
//b d b*d
Rational Rational::operator*(Rational rhs)//分数的乘运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*c;
int f = b*d;
return Rational(e,f);
}
//a c a d
//- / - = - * -
//b d b c
Rational Rational::operator/(Rational rhs)//分数的除运算
{
//rhs的分子分母互换
int t = rhs.numerator;
rhs.numerator = rhs.denominator;
rhs.denominator = t;
return operator*(rhs);
}
ostream& operator<<(ostream& os,Rational f)//并不属于Rational类,是一个独立的函数
{
if(f.numerator % f.denominator == 0)
os << f.numerator / f.denominator;
else
os << f.numerator << "/" << f.denominator;//打印分数
return os;
}
void call_love_fishc()//输出爱心
{
int i,j;
int n = 10;
for(i=1-(n>>1);i<=n;i++)//>>右移,右移一位除以2,左移一位乘以2
{
cout << "Hello World!";
if(i>=0)
{
for(j=0;j<i;j++)
cout << " ";
for(j=1;j<=2*(n-i)+1;j++)
cout << " *";
cout << endl;
}
else
{
for(j=i;j<0;j++)
cout << " ";
for(j=1;j<=n+2*i+1;j++)
cout << " *";
for(j=1;j<=-1-2*i;j++)
cout << " ";
for(j=1;j<=n+2*i+1;j++)
cout << " *";
cout << endl;
}
}
}
main.cpp文件
#include <iostream>
#include "includes\\fishc.h"//此处表示fish.h在includes目录中 .\\表示在当前目录 ..\\表示在上一级目录
#include "Rational.h"//系统级别用单尖括号,自定义级别用双引号
#include <string>
#include <math.h>
using namespace std;
int main()
{
call_love_fishc();//输出爱心
Rational f1(2,16);//定义f1对象,且传入(2,16)参数
Rational f2(7,8);
//测试有理数加法运算
cout << f1 << " + " << f2 << " == " << (f1+f2) << "\n"; // 左移操作符<<已经被重载了
//测试有理数减法运算
cout << f1 << " - " << f2 << " == " << (f1-f2) << "\n"; //<< f1中由于f1属于Rational类型,将自动转到打印分数形式输出
//测试有理数乘法运算
cout << f1 << " * " << f2 << " == " << (f1*f2) << "\n";//<< "+"中由于<<后面参数是字符串,所以使用系统默认的打印
//测试有理数除法运算
cout << f1 << " / " << f2 << " == " << (f1/f2) << "\n";
return 0;
}
第四十一讲:命名空间
命名空间其实就是由用户定义的范围,同一个命名空间里的东西只要在这个命名空间里具有独一无二的名字就行了。
例如:
namespace author
{
std::string person;
}
namespace programmer
{
std::string person;
}
两者由于在不同的命名空间,所以并不会出现冲突。
用一个using指令可以把特定命名从命名空间提取到全局作用域:
using std::cout;
cout << "Hello World!!\n";
using指令的出现决定着从命名空间里提取出来的东西能在哪个作用域内使用。如果把它放在某个函数声明的前面,他将拥有全局性;如果把他放在某个函数里,他将只在这一个函数里可以使用。
实例:
Rational.h文件:
//Ration.h
//Create by 亦我飞也
//这个头文件用来声明有理数类(Rational class)
//类里面对四则运算进行重载,以实现分数运算
//如果RATIONAL_H未被定义,则定义rational.h,执行定义类程序
//如果RATIONAL_H已经被定义,程序则直接跳到endif(即定义类程序只会执行一次)
#ifndef RATIONAL_H//如果没有定义rational.h
#define RATIONAL_H//定义rational.h
#include <iostream>
namespace myMath
{
class Rational//定义基类
{
public:
Rational(int num,int denom); //构造器 num = 分子,denom = 分母
Rational operator+(Rational rhs);// rhs == right hand side(右手边参数)
Rational operator-(Rational rhs);
Rational operator*(Rational rhs);
Rational operator/(Rational rhs);
private:
void normalize();//负责对分数的简化处理
int numerator; //分子
int denominator; //分母
friend std::ostream& operator<<(std::ostream& os,Rational f);//传递给它的是哪一个流,它返回的就是那个流的一个引用
};
void call_love_fishc();//输出爱心
}
#endif//如果已经定义rational.h,则结束
Rational.cpp文件
#include <iostream>
#include <string>
#include <math.h>
#include "Rational.h"//系统级别用单尖括号,自定义级别用双引号
using namespace std;
namespace myMath
{
Rational::Rational(int num,int denom)//构造函数实现
{
numerator = num;
denominator = denom;
normalize();
}
//normalize()对分数进行简化操作包括:
//1.只允许分子为负数,如果分母为负数则把负数挪到分子部分,如1/-2==-1/2
//2.利用欧几里德算法(辗转求余原理)将分数进行简化:2/10 => 1/5
void Rational::normalize()
{
//确保分母为正
if( denominator < 0)
{
numerator = -numerator;
denominator = -denominator;
}
//欧几里德算法
int a = abs(numerator);
int b = abs(denominator);
//求出最大公约数
while(b>0)
{
int t = a % b;//取余
a = b;
b = t;
}
//分子、分母分别除以最大公约数得到最简化分数
numerator /= a;
denominator /= a;
}
//a c a*d c*b a*d + c*d
//- + - = --- + --- = ----------
//b d b*d b*d b*d
Rational Rational::operator+(Rational rhs)//分数的加运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*b + c*d;
int f = b*d;
return Rational(e,f);
}
//a c a -c
//- - - = - + --
//b d b d
Rational Rational::operator-(Rational rhs)//分数的减运算
{
rhs.numerator = -rhs.numerator; //被减数分子取负数
return operator+(rhs);
}
//a c a*c
//- * - = ---
//b d b*d
Rational Rational::operator*(Rational rhs)//分数的乘运算
{
int a = numerator;
int b = denominator;
int c = rhs.numerator;
int d = rhs.denominator;
int e = a*c;
int f = b*d;
return Rational(e,f);
}
//a c a d
//- / - = - * -
//b d b c
Rational Rational::operator/(Rational rhs)//分数的除运算
{
//rhs的分子分母互换
int t = rhs.numerator;
rhs.numerator = rhs.denominator;
rhs.denominator = t;
return operator*(rhs);
}
ostream& operator<<(ostream& os,Rational f)//并不属于Rational类,是一个独立的函数
{
if(f.numerator % f.denominator == 0)
os << f.numerator / f.denominator;
else
os << f.numerator << "/" << f.denominator;//打印分数
return os;
}
void call_love_fishc()//输出爱心
{
int i,j;
int n = 10;
for(i=1-(n>>1);i<=n;i++)//>>右移,右移一位除以2,左移一位乘以2
{
cout << "Hello World!";
if(i>=0)
{
for(j=0;j<i;j++)
cout << " ";
for(j=1;j<=2*(n-i)+1;j++)
cout << " *";
cout << endl;
}
else
{
for(j=i;j<0;j++)
cout << " ";
for(j=1;j<=n+2*i+1;j++)
cout << " *";
for(j=1;j<=-1-2*i;j++)
cout << " ";
for(j=1;j<=n+2*i+1;j++)
cout << " *";
cout << endl;
}
}
}
}
main.cpp文件:
#include <iostream>
#include "Rational.h"//系统级别用单尖括号,自定义级别用双引号
#include <string>
#include <math.h>
//using指令的出现决定着从命名空间里提取出来的东西能在哪个作用域内使用。
//如果把它放在某个函数声明的前面,他将拥有全局性;如果把他放在某个函数里,他将只在这一个函数里可以使用。
using namespace std;
using namespace myMath;//声明命名空间myMath
int main()
{
call_love_fishc();//输出爱心
Rational f1(2,16);//定义f1对象,且传入(2,16)参数
Rational f2(7,8);//或者使用myMath::Rational f2(7,8);
//测试有理数加法运算
cout << f1 << " + " << f2 << " == " << (f1+f2) << "\n"; // 左移操作符<<已经被重载了
//测试有理数减法运算
cout << f1 << " - " << f2 << " == " << (f1-f2) << "\n"; //<< f1中由于f1属于Rational类型,将自动转到打印分数形式输出
//测试有理数乘法运算
cout << f1 << " * " << f2 << " == " << (f1*f2) << "\n";//<< "+"中由于<<后面参数是字符串,所以使用系统默认的打印
//测试有理数除法运算
cout << f1 << " / " << f2 << " == " << (f1/f2) << "\n";
return 0;
}
第四十二讲:链接和作用域
与作用域有关的另一个概念是链接,当同时编译多个文件时,每个源文件被称为一个翻译单元,在某一个翻译单元里定义的东西在另一个翻译单元里使用正是链接发挥作用的地方。
存储类(storage class):每个变量都有一个存储类,它决定着程序将把变量的值储存在计算机的什么地方、如何存储、以及变量应该有怎样的作用域。
链接(1、执行预处理器指令 2、把.cpp文件编译成.o二进制文件 3、把.o文件链接成一个可执行文件)
外链接:每个翻译单元都可以访问这个东西(前提是只要它知道有这么个东西存在)
内链接:在某个翻译单元里定义的东西只能在翻译单元里使用,在任何函数以外定义的静态变量都有内链接。
第四十三讲:链接和作用域2
header.h文件
#ifndef HEADER_H
#define HEADER_H
unsigned long returnFactorial(unsigned short num);
static const unsigned short headerNum = 5;//定义静态恒定值的全局变量
#endif
that.cpp文件:
#include "header.h"
unsigned short thatNum = 8;//定义全局变量
bool printMe = true;//定义全局变量
unsigned long returnFactorial(unsigned short num)
{
unsigned long sum = 1;
for(int i = 1;i<=num;i++)
{
sum *= i;
}
if(printMe)
{
return sum;
}
else
{
return 0;
}
}
this.cpp文件:
#include "header.h"
#include "iostream"
using namespace std;
extern unsigned short thatNum;//声明全局变量
static bool printMe = false;//定义静态全局变量
int main()
{
unsigned short thisNum = 10;
cout << thisNum << "! is equal to " << returnFactorial(thisNum) << "\n\n";
cout << thatNum << "! is equal to " << returnFactorial(thatNum) << "\n\n";
cout << headerNum << "! is equal to " << returnFactorial(headerNum) << "\n\n";
if(printMe)
{
cout << "小甲鱼真帅!\n\n";
}
return 0;
}
第四十四讲:函数模板swap使用
泛型编程技术支持程序员创建函数和类的蓝图(即模板,template),而不是具体的函数和类。
标准模板库STL(Standard Template Library),STL库是泛型编程技术的经典之作,它包含了许多非常有用的数据类型和算法。
当拥有一个模板时,编译器将根据模板自动创建一个函数,该函数会使用正确的数据类型完成相应的任务。
实例:函数模板
#include <iostream>
#include <string>
//using namespace std;
template <class T>//运用模板,告诉编译器,
//字母T在接下来的函数里代表一种不确定的数据类型
//以上class并不意味着这是类,而是约定俗成的写法
void swap(T &a,T &b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int i1 = 100;
int i2 = 200;
std::cout << "交换前,i1 = " << i1 << ",i2 = " << i2 << "\n";
swap(i1,i2);
std::cout << "交换后,i1 = " << i1 << ",i2 = " << i2 << "\n";
std::string s1 = "yiwofeiye";
std::string s2 = "helloworld";
std::cout << "交换前,s1 = " << s1 << ",s2 = " << s2 << "\n";
swap(s1,s2);
std::cout << "交换后,s1 = " << s1 << ",s2 = " << s2 << "\n";
return 0;
}
第四十五讲:类模板
类模板与函数模板非常相似,同样是先由你编写一个类的模板,再由编译器在你第一次使用这个模板时生成的实际代码。
实例:栈的出入栈
#include <iostream>
#include <string>
template <class T>
class Stack//栈类
{
public:
Stack(unsigned int size = 100);//构造器
~Stack();//析构器
void push(T value);//入栈
T pop();//出栈
private:
unsigned int size;
unsigned int sp;
T *data;
};
template <class T>
Stack<T>::Stack(unsigned int size)
{
this->size = size;
data = new T(size);
sp = 0;
}
template <class T>
Stack<T>::~Stack()
{
delete []data;//删除数组
}
template <class T>
void Stack<T>::push(T value)
{
data[sp++] = value;
}
template <class T>
T Stack<T>::pop()
{
return data[--sp];
}
int main()
{
Stack<int> intStack(100);//定义对象
intStack.push(1);//将1推入栈
intStack.push(2);//将2推入栈
intStack.push(3);//将3推入栈
std::cout << intStack.pop() << "\n";//弹栈
std::cout << intStack.pop() << "\n";//弹栈
std::cout << intStack.pop() << "\n";//弹栈
}
第四十六讲:内联模板
内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。编译时类似宏替换,使用函数体替换调用处的函数名。(在程序中,调用其函数时,该函数在编译时被替换,而不是像一般函数那样是在运行时被调用)
实例:栈
#include <iostream>
#include <string>
template <class T>
class Stack//栈类
{
public:
Stack(unsigned int size = 100)//构造器
{
this->size = size;
data = new T(size);
sp = 0;
}
~Stack()//析构器
{
delete []data;//删除数组
}
void push(T value)//入栈
{
data[sp++] = value;
}
T pop()//出栈
{
return data[--sp];
}
private:
unsigned int size;
unsigned int sp;
T *data;
};
int main()
{
Stack<int> intStack(100);//定义对象
intStack.push(1);//将1推入栈
intStack.push(2);//将2推入栈
intStack.push(3);//将3推入栈
std::cout << intStack.pop() << "\n";//弹栈
std::cout << intStack.pop() << "\n";//弹栈
std::cout << intStack.pop() << "\n";//弹栈
}
如果类模板需要一种以上的类型,可根据具体情况多使用几个占位符
template<class T,class U>
class MyClass
{
//
}
实例化(即定义对象)时:
MyClass<int,float>myClass;
第四十七讲:容器和算法
C++标准库提供的向量(vector)类型从根本上解决了数组先天不足的问题(内存固定,如果不用那么多内存编译器也会为其分配)
我们用不着对一个向量能容纳多少元素做出限定,因为向量可以动态地随着你往它里面添加元素而无限增大。还可以用它的size()方法查知某给定向量的当前长度(即包含的元素个数);用push_back()方法往它里面添加东西:
实例:向量容器应用
#include <iostream>
#include <string>
#include <vector>
int main()
{
std::vector<std::string> names;//定义一个字符串型的向量容器)
names.push_back("亦我飞也");//往里面添加东西
names.push_back("helloworld");
for(int i=0;i<names.size();i++)//size查知给定向量的当前长度
{
std::cout << names[i] << "\n";
}
std::cout << "size: "<<names.size() << std::endl;
return 0;
}
第四十八讲:向量和容器2
迭代器(iterator)是一种功能非常有限,却非常实用的函数,提供一些基本操作符。其是个所谓的智能指针,具有遍历复杂数据结构的能力。
实例:迭代器指针应用
#include <iostream>
#include <string>
#include <vector>
//迭代器iterator是个所谓的智能指针
int main()
{
std::vector<std::string> names;//定义一个字符串型的向量容器
names.push_back("亦我飞也");//往里面添加东西
names.push_back("helloworld");
std::vector<std::string>::iterator iter = names.begin();//定义迭代器iter为names的开始位置
//names.begin()调用的是names向量容器的第一个元素
while( iter != names.end())//names.ends()为names的结束位置
{
std::cout << *iter << "\n";//解引用,输出值
++iter;
}
std::cout << "size: "<<names.size() << std::endl;
return 0;
}