目录
第一章 预备知识
第二章 开始学习C++
1、名称空间
2、定义变量
3、赋值语句
第三章 处理数据
1、变量名
2、整形
3、浮点数
第四章 复合类型
1、字符串
a) 概念
b) 字符串输入
2、String类简介
a)相关函数
b)原始字符串
3、指针
4、C++数据存储
a)自动存储
b)静态存储
c)动态存储
5、内存空间
第五章 循环和关系表达式
1、延时循环
2、基于范围的for循环
3、cin读取之文件尾EOF
第六章 分支语句和逻辑控制符
1、字符函数库cctype
2、简单文件的输入输出
第七章 函数——C++的编程模块
1、函数和二维数组
2、函数指针
第八章 函数探幽
1、内联函数
2、函数参数的传递
3、默认参数
4、函数重载
5、函数模板
6、练习题
a)P299 5:
b)6:
第九章 内存模型和名称空间
1、单独编译
2、自动变量
3、静态存储持续性变量(全局变量+狭义静态变量)
a)链接性
b)静态变量的初始化
c)单定义规则(One Definition Rule,ODR)
4、练习题
a)P339第三题:
b)第四题
第十章 对象和类
想说的话:在学习过程中遇到我不了解的或者感兴趣的,会花大篇幅来讨论;对于很熟悉的内容在书上过一遍后,不会记录在本文中。这也算是对本科所学知识的一次系统的复习。编程环境:Visual Studio 2017
Stay hungry ,stay foolish.
工欲善其事,必先利其器。
泛型编程:generic programming
C语言的传统是头文件使用扩展名.h,C++的头文件则没有扩展名。
以厂商为例,名称空间指代不同的厂商,这样将多个厂商的代码组合时不会互相混淆。
也可以用using编译指令,using namespace Micro,使得Micro名称空间中的所有名称可用。
C++的做法是尽可能在首次使用变量前声明它。(为什么要声明变量?防止拼写错误而使用了错误的变量。。)
以两个下划线或下划线和大写字母开头的名称被保留给编译器,以一个下划线开头的名称也被保留,用作全局标识符。如_time_stop、_Dount虽然可以通过编译但不建议。
规定整形长度为:2Byte <= short <= 4B <= int <= long <= 8B <=long long
对于有符号整数,假设长为n位(bit),则其表示范围为 [-2^(n-1),2^(n-1)-1]。指数减一是因为留一位给符号位,结果减一是因为最高位为n-2。如signed short 范围为-2^15到2^15-1(-32768~32767) 。无符号数的表示范围扩大一倍即可。
如何判断溢出?逆向运算再比较:
int tadd_ok(int x,int y){ int sum=x+y; return (sum-x==y)&&(sum-y==x); }
cout< 浮点数表示带小数的数字,一部分表示值,另一部分用于对值进行放大和缩小。 在C++中,单引号表示字符常量,如 ‘S’;双引号表示字符串常量,如 “S”。 cin使用空白(空格、制表符和换行符)来确定字符串结束的位置,所以用cin输入“aaa bbb”时,第一个字符串接收aaa,第二个接收bbb,不能同时接受aaa bbb。要解决这个问题可以用getline() 和 get() 。cin.get() 经常用来接收输入流中未被接收的换行符。 参见 C语言字符串函数探幽 将原始字符串(Raw)输出,不用转义符。如下: cout<<R"(Jim "King" Tutt use "\n" instead of endl.) " << ' \n' ; cout<<R"+* ("(Who wouldn‘ t?)" , she whispered.) +*" < 一个指针的一生: 自动变量(局部变量)是在函数内部定义的常规变量,其作用域为包含它的代码块。执行到这个函数时,相关变量入栈,函数结束时,变量出栈,编译器自动释放这些变量。 第九章将详解。要么在函数外定义它,要么用static。 有一个称为堆的内存空间给程序员使用。可以在一个函数内分配内存,也可以在另一个函数里释放它。 函数名称 返回值 isalnum() 如果参数是字母数字,即字母或者数字,函数返回true isalpha() 如果参数是字母,函数返回true isblank() 如果参数是水平制表符或空格,函数返回true iscntrl() 如果参数是控制字符,函数返回true isdigit() 如果参数是数字(0-9),函数返回true isgraph() 如果参数是除空格之外的打印字符,函数返回true islower() 如果参数是小写字母,函数返回true isprint() 如果参数是打印字符(包括空格),函数返回true ispunct() 如果参数是标点符号,函数返回true isspace() 如果参数是标准空白字符,如空格、换行符、水平或垂直制表符,函数返回true isupper() 如果参数是大写字母,函数返回true isxdigit() 如果参数是十六进制数字,即0-9、a-f、A-F,函数返回true tolower() 如果参数是大写字符,返回其小写,否则返回该参数 toupper() 如果参数是小写字符,返回其大写,否则返回该参数 一切皆文件。 总结下: 函数指针的意义在于让程序在不同的时间使用不同的函数。例如sum程序,第一次传递乘法函数,第二次传递加法函数,避免每次手动修改sum程序的代码,有泛型编程的思想。 P252练习题10:编写使用函数指针的函数,要求使用指针数组来循环调用不同的函数。 常规函数的执行过程:执行到函数调用指令时,程序存储该指令的内存地址,把函数参数压入堆栈中,然后跳到函数起点的内存单元,执行函数代码,最后跳回到被保存的指令处,函数参数出栈,再继续运行函数调用指令的下一条指令。来回跳跃并记录跳跃位置意味着产生了一定的开销。 而内联函数在编译时,“函数调用”语句被替换为相应代码块,程序直接顺序执行而不是跳到别处。这样就节省了程序的开销,但需要占用更多的内存(造成代码的膨胀)。【以空间换时间】 实现方法:省略原型,把整个定义(函数头和函数体)放在本应提供原型的地方。 C语言有值转递(值拷贝,新建一个副本)和指针传递(传递参数的内存地址过去)。C++有值传递和引用传递(形参成为实参的别名)。 C++实现重载的机制是基于名称修饰(name decoration)(也好像是函数签名?): 1. C编译器的函数名修饰规则 __cdecl调用约定仅在输出函数名前加上一个下划线前缀。比如_functionname。 __fastcall调用约定在输出函数名前加上一个“@”符号。后面也是一个“@”符号和其參数的字节数,比如 @functionname@number 对于__stdcall方式,參数表的開始标识是“@@YG”,对于__cdecl方式则是“@@YA”。对于 __fastcall方式则是“@@YI”。參数表的拼写代号例如以下所看到的: 我们定义void func(int x) {} 和void func(float x) {},编译,查看汇编代码: 可以看出C++程序实际上存储了两个不同的函数。而从引用文字标红部分得知只能存储一个函数,无法重载。 另外,C++和C程序编译完成后在目标代码中名称修饰规则不同导致了一个问题:无法在C++程序中调用C的函数。解决方法是使用 extern “C”声明要引用的函数,告诉链接器在链接的时候用C函数名称规范来链接,如下: 函数模板可以将同一个算法用于不同类型的参数。【泛型】 编写模板函数,求出不同类型数组的最大值。 编写模板函数,返回数组中最大的元素,并具体化。(具体化不是很懂,是模板具体到一种指定的功能吗) 头文件里存放声明,源文件存放定义(实现)。头文件使用#ifndef---#define---//code---#endif来防止重复编译。 先来看一个例子: 变量a、b最开始拥有一片自己的内存。进入函数体后,系统新建了另一个同名变量a,赋值,压入栈中,更改b的值。离开函数体时,变量a出栈。举个例子:小明和小红手里都有一块钱。此时时间静止了(进入函数体),突然冒出一个也叫小明的人(入栈),他有两块钱,然后小红手里多了一块钱。这时后面的小明突然消失了(出栈),时间恢复到原来的地方(离开函数体)。小明手里还是一块钱,小红手里缺变成两块钱。 这说明,自动变量的作用域就是他们自己的代码块(函数体),但是产生同名变量时,作用域更大的那个会被屏蔽掉。 静态存储持续性变量有三种链接性: 所有静态存储持续性变量在整个程序的执行期间都存在,他们位于内存空间的数据段(data)而不是栈。 程序编译后,通过监视找到变量的内存地址,再去内存看看他们的数据: 发现系统把已初始化的静态变量存放在连续内存中(data段),未初始化的静态变量放在另一片连续内存中(bss段),且赋初值0(实际上这一块内存都已初始化为0,这叫做0初始化): 单定义规则指出变量只能有一次定义。C++为此提供了两种变量声明: 使用定位new运算符 这里引出一个问题:char *heap = new char[64]; sizeof(*heap)得到的是1,怎样得到64呢? 我的想法:heap是一个指向字符数组的指针,他的地址就是字符数组的首地址。不同于char stact[64],sizeof并不能知道heap结束的地址,所以只能输出1。这还得看看sizeof具体的原理。——2019.1.8 总结:C++能在指定的内存处使用内存,比C的内存调用机制又进了一步,这也是C++的奇妙之处。 名称空间: 总结:头文件写声明,最好使用预定义防止重复编译(#pragma once);对应的源文件写实现(定义);main文件作为入口包含应有的头文件就行。命名空间有种类的感觉。 OOP(Object Oriented Programming)的特性: 采用OOP方法,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。前者作为私有数据成员,后者作为公有成员函数提供访问数据的唯一途径。OOP强调程序如何表示数据。 类将部分数据隐藏,这就导致无法像初始化结构体一样初始化一个类。只能通过接口函数来给隐藏数据初始化。所以C++提供了一个特殊的成员函数——构造函数来实现初始化。 有两种使用构造函数的方法: 类中没有定义任何构造函数时,编译器会提供一个默认的空的构造函数——里面什么都没有。因此Stock food只会创建一个类而不是给它初始化。 这里要区分两个概念(还是不太懂): 1、默认构造函数:在未提供显式初始值时,用来创建对象的构造函数。适用于 Stcok food ; 程序员有两种定义默认构造函数的方法: 注意程序员未提供默认构造函数,但提供了非默认构造函数时, Stcok food 的声明会出错,因为编译器不再提供空的默认构造函数 2、非默认构造函数:如 Stock ( const string & co , int n); 析构函数完成对象生命期结束时的清理工作:主要是使用delete来释放构造函数中new的内存。如果构造函数没有new,析构函数实际无事可做。对于之前提到过的三种变量来说: 对于Stock类有三种初始化的方法: 最好不要创建对象的时候不赋值,后面再赋值,这样会产生临时变量(所以建议初始化时赋值): 现在要比较两个对象的某个值,返回最大的那个对象:比较obj1.val和obj2.val,如何实现这个函数呢? 每个对象的方法中都有一个隐藏的this指针,是作为参数传递进去的。this指针指向调用这个方法的对象。this的值也就是对象的起始地址。 传统的枚举例如: 里面的两个Small枚举量在同一作用域内,所以无法通过编译。正确做法是使用枚举类,把他们限制在各自的类中: 在实现乘法的运算符重载(Time operator*(double m)const;)时,可以做到A = B*2 ,但不能做到A = 2 *B,因为重载的运算符本质上是一个函数,B作为this指针,2作为double类型的参数,返回一个TIme类型值也就是A。那怎么办呢?只能通过非成员函数(类外函数)来解决。 也就是说在类外写一个方法:Time operator*(double m, const Time & t)来传入2和B这两个参数,返回结果A。这种方法就没有this指针可用,也就访问不了私有数据,即无法得到t.minutes 这个数据,这也是一个新的问题。所以前面要加上关键字friend 使其成为友元函数。 重载 * 和 << 重载运算符可以以成员函数或非成员函数实现,后者借助友元。 P423第7题 设计复数类 运行结果: C++让程序在运行时决定内存分配,而不是编译时(使用动态内存而不是一次性申请内存却浪费大部分)。 以下四种方式会调用复制构造函数。(因为复制了一个副本然后赋值?) 编译器自动声明的复制构造函数只能实现浅复制(指针复制),如果构造函数new了对象,需要重写来实现内存复制。 S1=S2; 先判断是不是自身,再删除自己的内存,再把S2复制到S1,返回S1 书中花了较大篇幅来描述一个String类,我开始觉得没什么意思,后来觉得有必要分析下,把声明写出来。 然后是Queue队列类。 先引入一个问题:类的私有变量中有一个常量:const int qsize,表示队列最大长度,想在创建对象时候初始化。那么应该在构造函数中给它赋值:qsize=xx ; 但是这就违背了一个原则:常量在被分配内存时就应该赋值。 例如 Queue q1,我们首先申请了一片内存,私有数据都有内存了,但是没有数值。然后调用构造函数来赋值。所以问题在于,我们应该在给常量申请内存时候就赋值,而不是等到构造函数体内赋值。解决方法是成员初始化列表,把所以数据提前到申请内存时赋值。 int game = 10 是等价于 int game (10) 的。 C++11允许类内初始化,与成员列表初始化等价: ' 代码在这里 最开始的类称为基类(始祖巨人),继承自基类的类称为派生类(进击の巨人)。派生类需要自己的构造函数,可以根据需要添加额外的数据成员和成员函数。 派生类的构造函数有两种写法: 创建时先创建基类,再创建派生类;析构时先析构派生类,再析构基类。 派生类不能使用基类私有的数据和方法,可以使用公有的和保护的。 派生类和基类指针转换有点难理解,举例如下: 始祖巨人Origin派生出C巨人Origin_C。C巨人只能用始祖巨人的公有/保护数据和方法,这个前面说过了。但是指向C巨人的指针可以隐式转换为指向始祖巨人的指针: 但是指针转换反过来就不行,始祖巨人怎么会知道C巨人的技能是什么呢? 总结:派生类可以看做基类的一个参数。对于派生类,他可以转换成指向基类的指针,从而使用基类的公有方法(也可以直接使用);对于基类,派生类可以作为他的参数,但是不能使用派生类的任何方法。公有继承建立一种is-a(派生类 is a kind of 基类)关系,即派生类对象也是一个基类对象,能对基类进行的操作,就能对派生类进行。 在派生类中重新定义基类的方法最好使用虚方法。 假设基类对象为origin,派生类对象为origin_c。他们都有一个同名方法show()和析构函数。现在有两个引用(或指针): 这样做是合理的,前面说过。o1和o2分别是对基类的引用,但注意,o2引用的对象是派生类的类型。那么使用show时会发生什么? 如果不使用virtual修饰,那么show方法都是基类的方法,析构函数也都是基类的析构函数(对一个派生类的引用使用基类析构可不好)。反之,给基类的show()使用virtual修饰(而不是派生类),o2.show方法会更聪明地使用派生的方法(因为o2引用的对象是派生类)。 总结:派生类可以被隐式转换为基类(用途:使用数组,统一存储各种派生类和基类),但是派生类又不是基类,不能使用基类的析构函数和同名函数,所以声明时用virtual修饰,到用的时候就会智能地选择对应的方法。 提示:通常应该给基类提供一个虚析构函数,即使基类不需要析构函数(为空)。 静态联编(早期联编)是指在编译时就确定该执行哪个函数,动态联编(晚期联编)是指在运行时确定(只针对虚方法)。 我们假设只有静态联编,对于o1和o2,编译器永远都执行origin类的同名函数,因为他不会去管o1、o2原本是对谁的引用/指向。 那么虚方法的实现就只能借助动态联编:每个对象新增一个隐藏成员(一个指向虚函数表(virtual functional table)的指针,虚函数表在运行时候生成吗?)。执行到同名函数show()时,通过该指针得到origin_c的虚函数表,找到虚函数表中对应的函数,执行。请看P504的图片。 解决了一个最重要的问题:引用/指向后,用一个指针指向的数组保留原始的状态。就算o2被转换为origin类型了,o2还有一个指针指向origin_c的虚函数表,存储origin_c类的虚函数地址,转而执行正确的虚函数。 再看看开销:使用虚函数,对每个类都得创建虚函数表这个数组(每个类所需存储空间增大)、每次调用虚函数都得去虚函数表中查地址。非虚函数就没有这些开销。 最后再来总结下如何正确使用虚函数:只在基类的同名方法前用virtual修饰(不用修饰派生类如果派生类永不派生、不用在实现中用virtual修饰)。返回类型协变:同名方法的参数必须相同,但返回类型可以不同,如下: 如果基类声明被重载了,应在派生类中重新定义: ABC至少要使用一个纯虚函数。ABC要求具体派生类覆盖其纯虚函数——迫使派生类遵循ABC设置的接口规则。 学到这里的时候,惊闻C++20要来了,而我还在学C++99/03……赶紧学吧。RunNoob~ 做第一题时,遇到一个Bug,记录下: 这里pcd指针指向的对象,它的虚函数表为空,导致无法指向对应的虚函数?为什么呢,我往前看,发现c1对象是有虚函数表的。但是c2初始化后就没有了,它的performance字段还被改了: 看看c2初始化干了什么: 我觉得是没问题的,然后看c1对象的内存,调试过程中发现c2初始化后c1有部分数据被改了,另外好像第一个字符串参数只存储了很少量的字符,然后回去看派生类的私有变量: 总结:因为写类变量时以为最多10个字符,没想到有30多个,导致存储空间不够,strcpy数组越界,把虚函数表的内存给覆盖了。这种问题感觉很难检查,花了很多时间,还是粗心,所以要用动态数组(第二题)。这个问题根源还是strcpy这个函数,它是不安全的。 做第二题的时候也有个Bug:派生类没有重写复制构造函数和赋值函数,导致派生类析构两次,报错。加上复制构造函数还是出差,再加上赋值函数OK。记住:赋值函数、复制构造函数是不能被继承的,一定要自己写。 今天也是被C++完虐的一天呢。 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可
3、浮点数
第四章 复合类型
1、字符串
a) 概念
b) 字符串输入
int main(){
//每次读取一行带空格的字符串
using namespace std;
const int size = 20;
char name[size];
char dessert1[size];
char dessert2[size];
cout << "enter name\n";
cin.getline(name, size);//通过换行符来确定行尾,存储时用空字符替换换行符
cout << "enter dessert1\n";
cin.get(dessert1, size).get();//通过换行符确定行尾,保留换行符,所以要再调用get()取出换行符
cout << "enter dessert2\n";
cin.get(dessert2, size).get();
cout << "end";
return 0;
}
2、String类简介
a)相关函数
b)原始字符串
3、指针
int main() {
/*1、声明指针。指针值均为0xcccccccc。造了三个仓库管理员,他们管理哪个仓库未知,也就是野指针*/
int *point_c,*point_cpp,*p,val;
/*2、定义|初始化指针。指针都有值了,但是他们指向的int变量均默认为-842150451。
仓库管理员知道自己管理哪个仓库了,但仓库里面没东西*/
point_c = (int *)malloc(sizeof(int *));
point_cpp = new int;
/*3、使用指针。指针指向的变量有一个明确的值了。仓库里面有货物了*/
*point_c = 10;
*point_cpp = 11;
/*4、释放内存。
指针指向的变量被清空,C指针指向的值为-572662307,C++指针也存在,但指向的值无法读取
他们变成野指针了(迷途指针):告诉管理员,你不再管理任何仓库了*/
free(point_c);
delete(point_cpp);
/*5、清空指针,指针值变为0
把管理员删除掉了。人没了。*/
point_c = NULL;
point_cpp = NULL;
return 0;
}
4、C++数据存储
a)自动存储
b)静态存储
c)动态存储
5、内存空间
第五章 循环和关系表达式
1、延时循环
#include
2、基于范围的for循环
double prices[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
for (double &x : prices)
x *= 0.8;
3、cin读取之文件尾EOF
int main() {
char ch;
int count=0;
cin.get(ch);
while (cin.fail()==false)//检测到EOF后,cin把eofbit和failbit都设置为1,cin.fail()为true
{
cout << ch;
count++;
cin.get(ch);//调试发现,键盘键入字符后回车,cin就从输入流读取,读完了就需要再次键入
}
while (cin.get(ch)) { ; }//这样也行
//注意在Unix使用Ctrl+D模拟EOF 而在Windows使用Ctrl+Z或回车
return 0;
}
第六章 分支语句和逻辑控制符
1、字符函数库cctype
2、简单文件的输入输出
#include
第七章 函数——C++的编程模块
1、函数和二维数组
int sum1(int(*arr)[4]) {//(*arr) 等价于 arr[]
return 1;
}
int sum2(int arr[][4]) {//两种传递参数的方式
return 1;
}
int main() {
int data[3][4] = { {1,2,3,4} ,{5,6,7,8}, {9,10,11,12} };
/*三行四列的二维数组
也可以理解为一个一维数组data[3],每个元素都是一个一维数组row[4]
data是可以扩展的,但是row不能扩展,必须指定每行有多少元素,也就是为什么在声明时必须指定
data + 1 即指向第一行
*(data+1) 为第一行的一维数组{5,6,7,8}
*( data + 1 ) + 1 指向第一行的第一个元素6
*( * (data + i) + j) 等于data[i][j]
*/
int n;
n = sum1(data);
n = sum2(data);
return 0;
}
2、函数指针
using namespace std;
void estimate(int lines, double(*pf)(int)) {
//pf是一个指向函数的指针,这个函数是什么样的呢?以int数据作为参数,返回double型数据
cout << (*pf)(lines) << endl;// 记住这样来使用函数指针
}
double fun1(int num) {
return 0.01*num;
}
double fun2(int num) {
return 0.02*num;
}
int main() {
estimate(10, fun1);//fun1作为参数,传递给了estimate函数
estimate(10, fun2);
return 0;
}
3、练习题
double add(double x, double y) {
return x + y;
}
double multiply(double x, double y) {
return x * y;
}
double sub(double x, double y) {
return x - y;
}
double(*pa[3])(double, double) = { add,multiply, sub };//函数指针数组,里面存了3个函数
double calculate(double x, double y, double(*pa)(double, double)) {//传递函数指针
return (*pa)(x, y);//函数指针的正确使用
}
int main() {
using namespace std;
double x, y;
do {
cin >> x >> y;
for (int i = 0; i < 3; i++) {
cout<<"method"<第八章 函数探幽
1、内联函数
2、函数参数的传递
3、默认参数
int add(int x, int y = 2, int z = 3) {//为y设置默认值,则y后面所有参数也要设置默认值
return x + y + z;
}
int main() {
std::cout << add(1) << add(1, 1) << add(1, 1, 1);
//输出1+2+3=6 、 1+1+3=5 、1+1+1=3
return 0;
}
4、函数重载
对于__stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其參数的字节数。比如 _functionname@number。
2. C++编译器的函数名修饰规则
C++的函数名修饰规则有些复杂。可是信息更充分,通过分析修饰名不仅可以知道函数的调用方式。返回值类型,參数个数甚至參数类型。无论 __cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”開始,后面紧跟函数的名字。再后面是參数表的開始标识和 依照參数类型代号拼出的參数表。
X--void
D--char
E--unsigned char
F--short
H--int
I--unsigned int
J--long
K--unsigned long(DWORD)
M--float
N--double
_N--bool
U--struct
....
指针的方式有些特别。用PA表示指针,用PB表示const类型的指针。//这里是C的头文件
extern "C" {
void fun1(int arg1);
void fun2(int arg1, int arg2);
void fun3(int arg1, int arg2, int arg3);
}
5、函数模板
//template
6、练习题
a)P299 5:
#include
b)6:
template
第九章 内存模型和名称空间
1、单独编译
2、自动变量
#include
3、静态存储持续性变量(全局变量+狭义静态变量)
a)链接性
int global1 = 0xFFFFFFFE;//外部可链接,不是可用(使用extern)
int global2;
static int one_file1 = 0xFFFFFFFD;//外部不可链接,但是该文件内所有代码可链接
static int one_file2;
void func() {
static int count = 0xFFFFFFFC;//只有变量所处的代码块才能链接
static int count;
}
b)静态变量的初始化
c)单定义规则(One Definition Rule,ODR)
//file01.cpp
int dog = 10;//定义
extern int cat = 10;//等价于int cat=10,定义
extern int mouse;//声明文件2的变量
//file02.cpp
int mouse = 1;
extern int cat;//声明文件1的变量
4、练习题
a)P339第三题:
#include
b)第四题
// main.cpp
#include
第十章 对象和类
1、构造函数
2、析构函数
3、初始化与赋值
Stock stock1("first", 12, 0.0);
Stock stock2 = Stock("second");//缺少参数则使用默认参数
Stock stock3{ "thrid",12 };//C++ 11
Stock stock3 = { "thrid",12 };// 等号可省略
Stock stock4;
stock4 = Stock("temp operator", 12, 0.0);//构造了一个临时对象,赋值给stock4,又把临时对象析构
4、this指针
const Stock & Stock::compare_val(const Stock & s) const//this指针作为参数传递进去了
{
/*解释下这三个const:
第一个表明返回的类型是Stock类的const引用,因为比较的两者都是const类型
第二个表明S的成员值不允许被修改
第三个表明*this也就是这个对象被const了,不允许修改
*/
if (s.total_val > this->total_val) return s;//this->total_val可省略为total_val
return *this;
}
//这样调用
Stock top = stock1.compare_val(stock2);
5、类中常量
private:
const int Months = 12;//这个类还没创建,没有空间来存放常量,所以这是错的
enum { Mouth = 12 };//方法1:这个枚举其实不是很理解
static const int Months = 12;//方法2:静态常量
6、枚举类
enum size {Small,Medium,Large,XLarge};
enum emergency {Small,big};
enum class size {Small,Medium,Large,XLarge};
enum class emergency {Small,big};
size s = size::Small;
第十一章 使用类
1、运算符重载
//.h
Time operator+(const Time &t)const;//声明
//.cpp
Time Time::operator+(const Time & t) const//注意格式
{
Time sum(0, 0);
sum.add_min(t.minutes);
sum.add_min(minutes);
sum.add_hour(t.hours);
sum.add_hour(hours);
return sum;
}
//main
Time t1(3,42);
Time t2(2);
Time t3;
t3.add_hour(25);
t3.add_min(2444);
t1.operator+(t2).show();
(t1+t2+t3).show();//有两种方法
2、友元
a)友元函数
//类中声明
friend Time operator*(double m, const Time & t);
friend std::ostream & operator<<(std::ostream &os, const Time&t);
//类外实现
Time operator*(double m, const Time & t){
//这样就可用使用t.hour t.minute这些隐藏数据了
return t * m;//这种方法实际还是调用乘法函数,也用到了t的隐藏数据
}
Time operator*(double m, const Time & t)
{
return Time();
}
//main中使用
Time t1;
Time t2(2,33);
t1=2*t2;
cout<
b)友元类
c)友元成员函数
3、练习题
//object.h
class complex
{
public:
complex();
complex(double r_,double v_=0);
~complex();
complex operator+(const complex &t);//this + t
complex operator-(const complex &t);//this - t
complex operator*(const complex &t);//this * t
complex operator~()const;// ~this
friend complex operator*(double m, complex &t);//3 * this(t)
friend std::istream & operator>>(std::istream &is, complex&t);
friend std::ostream & operator<<(std::ostream &os, const complex&t);
bool err = false;//设置一个错误指针 输入错误时为true 这里应该还能改进下
private:
double r;//实数部分
double v;//虚数部分
};
//object.cpp
complex operator*(double m, complex & t)
{
t.r *= m;
t.v *= m;
return t;
}
std::istream & operator>>(std::istream & is, complex & t){
cout << "real: ";
is >> t.r;
if (!t.r ) {//调试发现 输入q时,r不会有任何变化还是0
t.err = true;
return is;
}
cout << "imaginary: ";
is >> t.v;
return is;
}
std::ostream & operator<<(std::ostream & os, const complex & t){
os << "(" << t.r << "," << t.v << "i)";
return os;
}
complex::complex(){
r = 0.0;
v = 0.0;
}
complex::complex(double r_, double v_){
r = r_;
v = v_;
}
complex::~complex(){
}
complex complex::operator+(const complex & t) {
r += t.r;
v += t.v;
return *this;
}
complex complex::operator-(const complex & t){
r -= t.r;
v -= t.v;
return *this;
}
complex complex::operator*(const complex & t){
r *= t.r;
v *= t.v;
return *this;
}
complex complex::operator~()const {
complex temp=*this;
temp.v *= -1;
return temp;
}
//main.cpp
int main(int argc, char **argv)
{
complex a(3.0, 4.0);
complex c;
cout << "Enter a complex number (q to quit):\n";
while (cin>>c)
{
cout << "c is " << c << '\n';
cout << "complex conjugate is " << ~c << '\n';
cout<< "a is " << a << '\n';
cout << "a + c is " << a + c << '\n';
cout << "a - c is " << a - c << '\n';
cout << "a * c is " << a * c << '\n';
cout << "2 * c is " << 2 * c << '\n';
cout << "Enter a complex number (q to quit):\n";
if (c.err == true) break;
}
cout << "Done!";
return 0;
}
第十二章 类和动态内存分配
1、动态内存和类
2、复制构造函数
StringBad s;
StringBad s1(s);
StringBad s2 = s;
StringBad s3 = StringBad(s);
StringBad * p = new StringBad(s);
inline StringBad::StringBad(const StringBad & t)
{
count++;
str = new char[t.len + 1];
std::strcpy(str, t.str);
}
3、赋值运算符
StringBad &StringBad::operator=(const StringBad&t) {
if (this == &t) return *this;
delete[] str;
str = new char[t.len + 1];
std::strcpy(str, t.str);
return *this;
}
4、综合案例
a)String类
/*ADT:String类实现一个字符串类
数据:一个指向字符的指针,记录首地址?
字符串长度
总的字符串个数
方法:
赋值=
下标[]
比较==
输入输出<< >>
长度
*/
using std::ostream;
using std::istream;
class String
{
public:
//构造函数和方法
String();//默认构造函数
String(const char * s);//通过C风格字符串构造,注意不要全设默认值,这就变成默认构造函数了
String(const String &);//复制构造函数
~String();//析构函数
int length() const;//返回长度 这个const是修饰this指针的
//用成员函数重载运算符
String& operator=(const String&);//参数是一个String类的常引用,返回常引用
String& operator=(const char *);//C风格字符串的赋值 strcpy
char& operator[](int i);//return str[i]
const char& operator[](int i)const;//这里重载不是很理解,他们的内容一样,而且本来就是const的
//用友元函数重载运算符(这里什么时候用友元来重载还是不太清楚)
friend bool operator<(const String &st1, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st1, const String &st2);//strcmp
friend ostream& operator<<(ostream &os, const String &st);//把字符串st赋给输出流os
friend istream& operator>>(istream &is, String &st);//从输入流is读出数据,赋给字符串st
//静态函数
static int HowMany();//静态函数返回静态变量num_strings
private:
char *str;
int len;
static int num_strings;//计数
static const int CINLIM = 80;//静态常量,表示最大输入字符串长度
};
b)Queue类
Queue::Queue(int qs):qsize(qs),front(NULL),rear(NULL),items(0){}
class book{
int price = 10;
String name = "NULL";
}
5、练习题
a)P478 1:
class Cow
{
public:
Cow();
Cow(const char * nm, const char * ho, double wt);
Cow(const Cow &c);
~Cow();
Cow & operator=(const Cow &c);
void ShowCow() const;
private:
char name[20];
char * hobby;
double weight;
};
//实现
Cow::Cow()
{
}
Cow::Cow(const char * nm, const char * ho, double wt)
{
strncpy(name, nm,20);
int len = strlen(ho);
hobby = new char[len + 1];
strncpy(hobby, ho, len);
hobby[len] = '\0';
weight = wt;
}
Cow::Cow(const Cow & c)
{
strncpy(name, c.name, 20);
int len = strlen(c.hobby);
hobby = new char[len + 1];
strncpy(hobby, c.hobby, len);
hobby[len] = '\0';
weight = c.weight;
}
Cow::~Cow()
{
delete[] hobby;
hobby = nullptr;//清除内存后还要清空指针 不让其变成野指针
}
Cow & Cow::operator=(const Cow & c)
{
strncpy(name, c.name, 20);
int ho_length = strlen(c.hobby);
hobby = new char[ho_length + 1];
strncpy(hobby, c.hobby, ho_length);
hobby[ho_length] = '\0';
weight = c.weight;
return *this;
// TODO: 在此处插入 return 语句
}
void Cow::ShowCow() const
{
cout << "Cow name: " << name << endl;
cout << "Cow hobby: " << hobby << endl;
cout << "Cow weight: " << weight << endl;
}
b) 4:手写栈
第十三章 类继承
1、派生类和基类
class man{
public:
man();
man(int age_, int height_);
~man();
private:
int age;
int height;
};
class teacher :public man {
public:
teacher(char subject_, int age_, int height_);
teacher(char subject_, const man &m);
private:
char subject;
};
//实现
teacher::teacher(char subject_, int age_, int height_) :man(age_, height_)
{//使用所有数据初始化派生类
subject = subject_;
}
teacher::teacher(char subject_, const man & m) : man(m), subject(subject_)
{//使用基类初始化派生类
}
2、特殊关系
Origin_C c(……);
Origin &r = c; r.name();//r可以使用始祖巨人的技能
Origin * p = &c;c->name();
3、使用虚函数实现多态公有继承
origin& o1 = origin ;
origin& o2 = origin_s;
4、动态联编——虚函数的实现
5、虚函数的形式
class man{
public:
virtual man& show (int n);
}
class tecaher : public man{
public:
virtual teacher& show (int n);//或返回类型为man&的
}
class man{
public:
virtual void show (int n);
virtual void show (int n) const;
virtual void show (int n ,int m);
}
class tecaher : public man{
public:
virtual void show (int n);
virtual void show (int n) const;
virtual void show (int n ,int m);
}
//实现
void teacher::show (int n){
man::show() ;
//code
}
6、抽象基类(Abstract Base Class——ABC )
7、练习题
第16章 string类和标准模板库
string类
string类的构造函数
#include
string类的输入
#include
string类的使用
#include