黑皮书《C++程序设计》随书笔记

C++程序设计

序言及感想

蒟蒻现系大二,本次博客应该是蒟蒻第一次发博客,因此不免感慨万千。遂记于此。
蒟蒻资质庸愚,自愧不如于人多矣,大一上浑浑噩噩,程设创历史最低,大一下更为躺平,计组创历史新低。假期望弥补过去,改过自新,然假期不觉已过半矣。嗟乎,年与时驰,意与日去,痛定思痛之时,遂立发博客之目标,以鞭策己身耳。

本人系小镇做题家,高考后零基础盲选计算机,大一时期常感迷茫困顿,自感资质愚钝又不知如何努力,致使成绩不在保研线内矣。在一年的学习之后,回过头来再看黑皮书《C++程序设计》不免感慨万千,甚有所得,遂记于此。

《C++程序设计》作为C++语言的入门书籍,又是被众多大神吹捧的黑皮书。本蒟蒻心头一热,遂买。现读完并综合一年所学,总结如下:
整本书主要分为三个部分,框架也是很明晰的,分别为编程基础,面向对象编程和算法与数据结构。
第一部分的编程基础这本书还是蛮简练的,在一年后的复习中,我也将书中的这一部分(前八章)的重点摘录出来,在这篇博客内所占篇幅比例较小,但还是比较重要的,毕竟编程基础都不知道的话还怎么编程啊
第二部分的面向对象这一部分相对而言就有抽象了,因为本人在大一下加入了学校的ACM队并参加了寒假加半个学期的集训,所以对编程基础和算法与数据结构这两部的感悟最大,也感到最亲切。而第二部分的,面向对象编程相对而言实践的机会几乎为0(狗头)(小丑),所以现在的状态是如看,但综合感觉其实类与结构体相关的概念其实和前面的相差不大,但因为没有应用来实操,所以感觉如看。总之,我在这篇博客里,也把重点放在了概念的理解上。所以篇幅略长。我还是建议对于第一次看黑皮书的任何书时,第一遍看时没有必要认认真真全全看一遍,大至浏览一下,能到有应用的场景时,回过头再来看书,其中很多便会恍然大悟。如果为了应对学校中的程设考试的话,在第二部分也主要涉及像类,对象,继承,多态等重要概念的理解。
对于第三部分,对于计算机专业学校之后基本会开设数据结构与算法课程,而且数据结构与算法真的非常非常非常重要,以后想进大厂面试或者笔试是必考的。如果感觉自己合适可以考虑加入学校的ACM参加ICPC,CCPC,觉得自己比较差劲的也要好好学,可以参加蓝桥杯,华为杯,天梯赛,百度之星等一众个人赛。读到这一部分时,一定要多刷题。可以先初步刷刷学校OJ上的题库,然后进一步的可以到atcoder,coderforces等网站上参加每周的比赛,牛客网,洛谷以及力扣都可,都有大量的题库专题,如果你有时间在大一一定要抽出多写写代码。

写到这里,本篇博客前90%已经讲完啦,后面的东西其实都是水货,前面的经验才是无价之宝。后面的这10%的东西,我写出来是为了鞭策自我的,后面的内容也主要更侧重学完之后查漏补缺吧,初学的话也可以看看,如果你很聪明的话,第一部分编程基础看了我的总结就不用看课本了,多刷题就OK了。
第一部分总结的勉强能看,第二部分随(基)便(本)看(不)两(用)眼(看)就可以了 ,因为我也没怎么弄懂,相关概念不懂的话可以看看网课(黑马之类的),也可以在csdn上对相关概念进行检索(因为我这个算是一个随书笔记吧,都很粗略,对于大佬总结的各个专题,是根本比不了的),更简单的方式是交给GPT,yyds好吧。

总之呢,这本书也可圈可点,关于C++入门的其他可能更好书籍推荐我也给不了相关的建议,因为我只看过这一本。多看看各位大佬的建议,但更重要的是学习,行动。若干年后,我看到这篇博客一定会感慨年少的幼稚的。

分割线

总之,后面的粗略看看就行。

1,学习有四种境界:

学了,懂了,解决了,讲了

学一学不过是沉下心付出时间就可以了,若想要懂了就需要刨根问底,继续深究了,学懂之后好要能够用出来解决问题,最后就是能够用自己的话把这本书的内容看着目录能够讲出来。本人所处的阶也不外乎是学了的阶段,也就是半吊子的水平。这本书其实还好,更偏向于编程实践,鼠鼠接下来要学的计组就更偏向于概念理解以及框架方面,能够到达讲了的阶段才算是真正的学会。

如果想要学懂的话,把每一节的内容放在csdn上基本都能搜到大佬总结的超详细的解释与回答。还是得看你能不能静下心来把每一点都学实了(这一点还是很难很难的,受限于有限的时间以及精力还有性价比())。

2, 整本书学下来,其实并没有达到掌握很好的水平,因为每一章的课后练习真的好难(qaq),不过总有佬们能很轻松地解决这些问题,鼠鼠们继续加油吧。

提示:

1,黑皮书每章后面的本章小结很全面。

2,第二部分中,一些很重要的要点:第10章中string类,11章中的指针相关,12章中的通用模板类vector

第一部分:编程基础

第一章 计算机,程序和c++语言简介

1.1 计算机基本组成

存储器分为

磁盘驱动器

光盘驱动器(CD(光盘)和DVD(数字光盘))
USB闪存驱动器(USB:通用串行总线)

驱动器是能够操作磁盘和光盘等存储介质的设备。

显示器:

分辨率和点距决定了显示的质量
分辨率:指显示器每平方英寸的像素数量。分辨率越高,显示越锐利清晰。
点距:是指像素间的间隔大小,以毫米度量。点距越小显示越锐利。

通信设备:

  • 拨号调制解调器:使用电话线传输数据,速度可达56000bps
  • DSL(数字用户线路):也使用电话线,但数据传输速度可达拨号调节器的20倍
  • 线缆调制解调器:使用有限电视线路传输数据
  • 网卡(NIC):将计算机接入局域网中
  • 无线网络适配器

1.2 编程语言

C# :(读作:C sharp)类Java和C++的编程语言

用高级语言编写的程序称为源程序或源代码

1.3 操作系统

操作系统的主要任务包括:

  • 控制和监视系统活动
  • 分配和指派系统资源
  • 任务调动

1.4 C++程序开发流程

在Windows上,机器码文件在磁盘中储存为.obj文件,可执行文件存储为.exe文件。在UNIX上,机器码文件有一个.o的扩展名,可执行文件没有文件扩展名。

IDE表示集成开发环境,是便于程序设计的工具软件,如Visual C++,Dev-C++等等。

第二章 程序设计原理

1,标识符是程序中定义类似变量,函数之类元素的<名字>(不能以数字开头,可以包含字母数字和下划线)int sum; sum 即为标识符
2,cout << x=1; //输出为1;  //<<流插入运算符  >>流提取运算符
3,<limits>头文件中定义了INT_MIN;INT_MAX;LONG_MIN;LONG_MIN等等
#include<limits>
cout<<"INT_MIN "<<INT_MIN<<endl;

可以使用sizeof函数来查看一个类型或者变量在当前机器上所占的大小

4,八进制整数使用前缀0,十六进制整数常量使用前缀0x或0X。

5,%运算符的运算对象只能是整数
单目运算符:只有一个操作数的运算符
指数运算符:pow(a,b);用来计算a的b次方,其在cmath库文件中定义
6,可以调用ctime中的time(0)函数返回自格林尼标准时间1970年1月1日00:00:00(称为UNIX纪元)至当前时刻所流逝的秒数
7,自增自减运算符:++i;表示先将i的值增加1,并使用加1后的新值进行后续计算
i++;将i的值加1,但使用i的原值进行运算;
例:int i=1; int j=++i;//j is 2,i is 2;
int i=1; int j=i++; //j is 1;i is 2;

8,数值类型转换
静态类型转换可以用(type)语法来完成,即给出括号例的目标类型,跟随一个值,一个字面常量或者一个表达式。这叫做C类型转换。
int i =(int)5.4;
等价于:int i =static_cast<int>(5.4);

第三章 分支语句

1,cmath中的函数abs(a)可以返回a的绝对值
2.三目运算符:num1>num2?num1:num2;
3,运算优先级:算术运算符(+-*%)>关系运算符(>=<)>逻辑运算符(&|)
4,生成随机数,头文件<cstdlid>rand()函数
这个函数返回一个在0~RAND MAX之间的随机数。RAND MAX是一个平台决定的常数,在Visual C++中,RAND_MAX是32767
rand()函数生成的是伪随机数,即每次在同一个系统上执行这个函数时,rand()函数生成同一序列的数。如在我的计算机上生成的前三个数恒为6334 18467 41
因为rand()函数是用一个叫种子的值来控制生成数字。默认情况下种子的值为1
可以使用cstdlib函数中的scrand(seed)来改变种子的值,为了确保每次种子的值不同,可以用time(0);
如:scrand(time(0));
cout<<rand()<<endl;
5,switch语句中case语句开始执行后续语句,直到遇到一个break语句或直到switch语句末尾。

第四章 数字函数,字符和字符串

1,cmath头文件中,三角函数:sin(radians),cos(radians),asin(a);
指数函数:exp(x):返回e的x次方的值。log(x)返回自然对数的值(In(x));log10(x);pow(a);sprt(x)返回x的平方根(正的)
近似函数:ceil(x):x向上取整到一个最接近它的整数。此整数为double类型的值
         floor(x):x向下取整到一个最接近它的整数。此整数为double类型的值
min函数,max函数,abs函数//在GUN C++中min,max,abs函数都定义在cstdlib头文件下而在Visual C++中min和max定义在algorithm头文件下
2,ASCII码
空格:32
'0'~'9':48~57
'A'~'Z'
65~90
'a'~'z'97~122
3,字符函数(头文件<cctype>)
isdigit(ch)如果指定的字符是数字,则返回true
isalpha(ch):字母
isalnum():字母或数字
islower():小写字母
isupper():大写字母
isspace():空白字符
tolower():返回指定字符的小写形式
toupper():返回指定字符的大写形式
4,getline函数(<string>头文件)
可以输入空白字符
getline(cin,s,'\n')//getline函数的第三个参数的默认值为'\n',所以可省略getline(cin,s)
5,格式化控制台输出
C++提供了附加函数来格式化一个要显示的值,这些函数叫做流操作,包含在iomanip头文件中。
setprecision(n):设定一个浮点数的精度
fixed:显示指定小数位数的浮点数
showpoint:即使没有小数部分也显示以零补足的小数点后位数
setw(width):指定打印字段的宽度
left:调整输出到左边
right:调整输出到右边
//setprecision(n):其中n是所需数字位数,小数点前后位数的总和。
double num=12.34567;
cout<<setprecision(3)<<num<<' '//12.3
<<setprecision(4)<<num<<' '//12.35
<<setprecision(5)<<num<<' '//12.346
    setprecision操作的作用是直到精度改变之前,一直保持效果
 
 当fixed与precision(n)一起使用时,n指的是小数点后的位数
    
    //showpoint操作
//也看可以用showpoint与setprecision组合可以用来补充小数点后的位数
    
cout << setprecision(6);//单用setprecision的话确定的是数字的位数,不是小数点后的位数
cout << 1.23 << endl;
//setprecision不会补充小数点后缺少的0位数
cout << showpoint << 1.23 << endl;
//输出为1.23000
cout << showpoint << 123.0 << endl;
//输出为123.000
    
	//setw(width)操作      //默认为右对齐
	//指定输出的最小列数(宽度)
	cout << setw(8) << "C++" << setw(6) << 101 << endl;
    cout << setw(8) << "Java" << setw(6) << 101 << endl;
	cout << setw(8) << "HTML" << setw(6) << 101 << endl;
	//表示”C++“这个字符串的宽度为8,所以输出前有5个空格”C++“这个字符串占3个空格
	//用于控制输出之间的间隔
	//输出为
//     C++   101
//    Java   101
//    HTML   101

	


	//setw操作积极影响下一次的输出
	cout << setw(8) << "C++" << 101 << endl;
//     C++101

	//若字符串的宽度比指定的宽度要长,则宽度会自动增加
	cout << setw(9) << "Programming"<<"#" << setw(2) << 101<<endl;
	//输出为Programming#101
//setw默认为右对齐,可通过left来实现左对齐

	//left和right操作
	cout << left;
	cout << setw(8) << 1.23 << 351.34 << endl;//用/代表空格,输出为:1.23351.34//

4.11简单的文件输入输出(P125)

	//创建一个文件来保存数据,并之后读取这个文件的数据


	//写入文件
    //要向文件中写入数据,首先要声明一个ofstream类型的变量:
	//ofstream在fstream头文件
	ofstream output;//声明一个ofstream的变量
	output.open("numbers.txt");//为指定一个文件需要调用output对象的open函数
		//此条语句创建了一个叫number.txt的文件,如果文件已经存在,内容将会销毁,重新创建这个文件。调用open把文件和流关联起来。
	//ofstream output("number.txt");//创建一个输出对象和打开这个文件
	output << 95 << " " << 56 << " " << 34 << endl;//这句话把95 56 34写入文件,中间用空格隔开
	//为插入数字,应使用流插入操作符<<
//当输入文件完成后,调用output对象的close函数
output.close();
	
	//读取一个文件
	ifstream input;
	input.open("number.txt");//if如果试图打开一个不存在的文件。将会出现unexpected error的提示
	//可以用一句语句创建一个文件输入对象并打开文件:ifstream input("numbers.txt")
	//为读取数据,使用输出流操作符>>
	int score1, score2, score3;
	input >> score1 >> score2 >> score3 ;
	//需要调用input的close函数
	input.close();
	//理解就行,没有输出结果

//为了从一个文件中读数据可以创建一个ifstream对象,而为了向一个文件写入数据则可以创建有一个ofstream变量

第五章 循环

输入输出重定向(P141):

//如果有许多数据需要输入,从键盘键入就会很麻烦。可以在一个文件中存储数据,使用空格分割开,如:input.txt,然后用下面的命令运行程序
SentinelValue.exe < input.txt
    //这个命令叫做输入重定向
    
    //SentinelValue.exe可以通过编译器命令行获取
    g++ SentinelValue.cpp -o SentinelValue.exe
    
    //同样的,输出重定向可以将输出发送到一个文件中,而不是在控制台显示
    //输出重定向的命令是
    SentinelValue,exe > output.txt
    
    //输入输出重定向可以在一个命令中使用
    SentinelValue.exe < input.txt > output.txt

从一个文件中读取所有的数据(P142)

//如果不知道一个文件中有多少数据,但想要全部读入,如何确定已经到了文件的末尾?
//可以调用input对象的eof()函数来检测

//当没有可读取的内容时,eof函数返回true
//为了这个 程序正确运行,文件中最后一个字符后面不能有空白字符
//程序如下:
#include<iostream>
#include<fstream>
using  namespace std;

int mian(){
    ifstream input("numbers,txt");
    
    double sum=0;
    double number;
    while(!input.eof()){
        input >> number;
        cout<<number<<" ";
        sum+=number;
    }
    input.close();
    
    cout<<"\nSum is "<<sum<<endl;
    return 0;
    
}

do-while循环

do-while 循环
	//区别:do-while先执行循环体,后对循环继续条件进行求值,
	//而while循环先判断条件是否成立再决定是否执行
	int i = 0;
	do {
		cout << "i is " << i << endl;
		i++;
	} while (i < 10);

第六章 函数

函数的重载

//函数的重载
//创建另一个函数,使用相同的名称,但使用不同的参数

#include<iostream>
using namespace std;

int max(int num1,int num2) {
	if (num1 > num2) {
		return num1;
	}
	else
		return num2;
}
double max(double num1, double num2) {
	if (num1 > num2) {
		return num1;
	}
	else
		return num2;

}

//调用时函数会匹配最合适的函数类型
//如果设置模糊,编译器有时会发生模糊调用的编译错误

函数原型

函数原型:没有函数实现的单纯的函数声明
int max(int num1,int num2)
//也可以只列出参数的类型
int max(int,int)

缺省参数

缺省参数
如果函数调用中未给出参数,那么参数的缺省值将被传递给函数
#include<iostream>
using namespace std;

void printArea(double r = 1)//定义缺省值为1,在没给出参数的情况下,会自动赋值为1
{
	double area = r * r * 3.14159;
	cout << area << endl;
}
int main() {
	printArea();//输出为3.14139,缺省值被传递给函数
	printArea(4);
		return 0;

}
//在函数的参数中,有的设置了缺省值,有的没有,带缺省值的参数应该放在参数列表的末尾
//如:下面的是不合法的
//void t1(int x, int y=0.int z)
//当调用一个函数时,如果一个参数未给出,那么在它之后的参数也不能给出
如:t3(1, ,20)//是不合法的

内联函数

内联函数
//C++编译器会拓展每个对内联函数的调用,将函数的代码复制过来替换调用(感觉用处不大)
#include<iostream>
using namespace std;
inline void f(int month, int year) {
	cout << month << endl;
	cout << year << endl;

 }
int main() {
	int month = 10, year = 2008;
		f(month, year);
		f(9, 2010);
		return 0;
}

//其实表面上除了关键字inline关键字外,内敛函数与普通函数没有什么不同,,,,

局部,全局和静态局部变量

//局部变量没有缺省值,而全局变量的缺省值为0;
//静态局部变量的作用:有时,我们需要保留局部变量的值,以便在下次时继续使用
#include<iostream>
using namespace std;

void t1();

int main() {
	t1();
	t1();
	return 0;
}
void t1() {
	static int x = 1;//定义了一个静态局部变量x,
	int y = 1;
	x++;
	y++;
	cout << x << endl;
	cout << y << endl;


}
//输出为2 2 3 2
//第一个t1()输出为2,2;而x被定义为静态局部变量,调用后其值会保留在内存中,
//第二次t1()被调用时,输出为3,2;

//其实也可以把一个变量放在循环的外面也能达到相同的效果

以引用的方式传递参数

值传递
    不论将实参赋予形参后,形参做了什么样的变化,实参的值仍然不会变,被赋予实参的形参的值会改变
引用传递
    int &r=count;//声明了一个引用变量r来引用变量count
//或者 int& r=count
//例如
#include<iostream>
uisng namespace std;
int main(){
    int count =1;
    int &r=count;
    r++;
    cout<<r<<" "<<count<<endl;//r和count都为2;
    //r相当于count的一个别名
     
    
    
    return 0;
}

常量引用参数

将const加在函数的形式参数声明之前
//如果程序使用了一个传递引用的参数,参数在函数中不能改变,应该把这个变量设置为常量。
int max(const int& num1,const int &num2)

第七章 一维数组和C字符串

//数组大小必须是常量表达式
//如下面的代码是非法的

//int size = 4;
//double mylist[size]
//而如下定义则合法
//const int SIZE;
//double mylist[SIZE]

C字符串

C字符串是一个字符数组,以'\0'(空终结符)结尾【这个字符是ASCII码中的第一个字符】
//注意:下面两行的代码是不相同的
    char city1[]="Dallas";
	char city2[]={'D','a','l','l','a','s'};
//第一条语句是一个C字符串,一共有7个字符(最后有'\0'空终结符)
//第二条语句是字符数组,有6个字符


cin.getline函数

//头文件<iostream>
//用cin.getline函数读取一个字符串
//这样就可以读取空格了
char city[30];
    cout<<"Enter a city:";
cin.getline(city,30,'\n');//第三个参数的缺省值为'\n',故可省略
cout<<"You entered "<<city<<endl;

C字符串函数

定义在头文件下

size_t strlen(char s[])    //返回字符串的长度,即在空终结符之前的字符个数
strcpy(char s1[],const char s2[])   //将字符串s2复制到s1中
strncpy(char s1[],const char s2[],size_t n) //将字符串s2中前n个符号复制到s1中
        //例:strncpy(city,"New York",3);  将New复制给city字符数组中
strcat(char s1[],const char s2[])   //将字符串s2拼接到s1之后
strncat(char s1[],const char s2[],size_t n)  //将字符串s2的前n个字符拼接到s1之后
    //strcat(s1,s2);
int strcmp(char s1[],const char s2[])  //通过字符的数值码对比,判断s1大于,等于或小于s2,分别返回一个大于0的数,0,小于0的数
    //比较每个字符对应的ASCII码,即按字典序比较
int strncmp(char s1[],const char s2[],size_t n)  //类似于strcmp函数,但是指定了s1和s2中的n个字符

定义在头文件下

以下的算是转换函数

int atoi(char s[])  //返回字符串对应的int型值
    //例:
    char s1[]="65";
char s2[]="4";
cout<<atoi(s1)+atoi(s2)<<endl;
//输出为69

double atof(char s[])  //返回字符串对应的double型值
long atol(char s[])  //返回字符串对应的long型值

    void itoa(int value,char s[],int radix)  //获得一个字符串的整数值,基于一个指定的基数
    
    //使用itoa函数将一个整数转化为一个C字符串,使用atoi将一个字符串转换为一个整数
    
    //例
    char s1[15];
char s2[15];
char s3[15];
itoa(100,s1,16);
itoa(100,s2,2);
itoa(100,s3,10);  //后面的16,2,10是做基于的指定基数,简单理解来说算是一种运算函数
cout<<"The hex number for 100 is "<<s1<<endl;//十六进制数
cout<<"The binary number for 100 is"<<s2<<endl;//二进制数
cout<<"s3 is "<<s3<<endl;

第八章 多维数组

二维数组

二维数组要求在函数参数类型声明中指明列的大小

第二部分:面向对象编程

第九章 对象和类

一个类还提供了一些特殊类型的函数,在创建对象时这些函数会被调用,我们称这些函数为构造函数
UMI(unified Modeling Language)  统一建模语言

构造函数

构造函数的名字必须与类名相同
构造函数没有返回类型——即便返回void也不可以
在创建对象时,构造函数被调用,它的作用就是初始化对象
一个类成员,数据域是不能在声明时进行初始化的
例如在类内定义数据域,double radius; 如果改为double radius=5;则是错误的
C++面向对象的三大特性:封装,继承和多态
封装的意义:
1,
2,类在设计时,可以把属性和行为放在不同的权限下,加以控制和管理
#include<iostream>
using namespace std;
//设计一个圆类,求园的周长


const double PI=3.14;

class Circle{//类+类名

    //访问权限
    public    
        
        //类中的属性和行为我们统一称为 成员
        //属性 成员属性 成员变量
        //行为 成员函数 成员方法
        
        
    //属性
    //半径
        int m_r; 

    //行为
    //获取圆的周长
    double calculateZC(){
return 2*PI*m_r;
    }
    
};   //不要忘记在类定义末尾需要一个分号
int main(){
 //通过圆类,创建具体的圆(对象)
    //实例化过程
    Circle c1;
    //给圆对象的属性进行赋值
    c1.m_r=10;
    
    cout<<"圆的周长为:"<<c1.calculateZC()<<endl;
    
    return 0;
}

访问权限

//公共权限 public       成员类内可以访问,类外也可以访问
//保护权限 protected    成员类内可以访问,类外不可以访问
//私有权限 private      成员类内可以访问,类外不可以访问

class 和 struct区别

struct默认的访问权限为共有
class默认的访问权限为私有

成员属性设置为私有

优点1:将所有成员属性设为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性

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

//人类 
class Person{
    public:
    //设置姓名
    void setName(string name){
        m_name=name;
    }
    //获取姓名
    string getname(){
        return m_name;
    }//综上:姓名可写可读
    
    //获取年龄
    int getAge(){
return m_Age;

    }
    //综上:年龄可读不可写
    //偶像可写不可读
    void setIdol(string idol){
        m_Idol=idol;
    }
    
    private:
    string m_name;//姓名  可读可写
    int m_Age=18;//年龄  只读
    string m_Idol;//偶像 可写
}

int main(){


return 0;
}

对象的初始化和清理(构造函数和析构函数)

构造函数:主要作用在创建对象时为对对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

构造函数语法:类名(){}
1,构造函数没有返回值,也不写void
2,函数名称与类名相同
3,构造函数可以有参数,因此可以发生重载

析构函数语法:~类名(){}
1,析构函数没有返回值,也不写void
2,函数名称与类名相同,在名称前加上~符号
3,析构函数不可以有参数,因此不可以发生重载
 class Person{
    public:

    Person(){
    cout<<"Person 构造函数的调用"<<endl;
}   
//2,析构函数进行清理的操作
 ~Person(){
    cout<<"Person 构造函数的调用"<<endl;
}   
};
    void test01(){
Person p;
}//构造函数会自动调用,test01函数并没有调用操作
//析构函数会自动调用


构造函数的分类及调用

两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
#include<iostream>
    using namespace std;

//构造函数的分类及调用
//分类
//按参数分类:无参构造和有参构造
//按照类型构造:普通构造和拷贝构造
class Person{
    //构造函数
  public:
      Person(){
        cout<<"Person的构造函数调用"
    }
    //无参构造
    
      Person(int a){
        cout<<"Person的构造函数调用"
    }
    //有参构造
    
    ~Person(){
        cout<<"Person的析构函数调用"
    }
    //拷贝构造函数:
      Person(const Person &p){
age=p.age;
    }
}
//调用
void test01(){
    //1,括号法
    Person p;//默认构造函数调用
Person p(10);//有参构造函数调用
    //拷贝构造函数
    Person p3(p2);
    
    //注意事项1
    //调用默认构造函数时候,不要加()
    //因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
    Person p1();
    
    //2,显示法
    Person p1;
    Person p2=Person(10);//调用有参构造
    
    Person p3=Person(p2)//拷贝构造
        
        Person(19);//匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象
    
    //注意事项2
    //不要利用拷贝构造函数 初始化匿名对象
    Person(p3);//这种语法是错误的,不能来匿名对象
    //编译器会认为 Person(p3)==Person p3是对象的声明;
    
    //3,隐式转化法
    Person p4=10;//相当于 写了 Person p4=Person(10);
    Person p5=p4;//拷贝构造
    
}

拷贝构造函数调用时机

调用时机
//使用一个已经创建完毕的对象来初始化一个新对象
    //值传递的方式给函数参数传值
    //以值方式返回局部对象
    #include<iostream>
    using namespace std;
 class Person{
public:
     Person(){
cout<<"Person默认构造函数调用"<<endl;
     }
 Person(int age){
     cout<<"Person有参构造函数调用"<<endl;
m_Age=age;
 }
     Person(const Person &p){
         cout<<"Person默认构造函数调用"<<endl;
         m_Age=p.m_Age;
     }
     ~Person(){
         cout<<"Person析构函数调用"<<endl;
     }
     int m_Age;
 };
//使用一个已经创建完毕的对象来初始化一个新对象
void test01(){
    Person p1(20);
    Person p2(p1);
    cout<<"P2的年龄为; "<<p2.m_Age<<endl;
    
}
//值传递的方式给函数参数传值
void dowork(Person p){
}

构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数
1,默认构造函数(无参,函数体为空)
2,默认析构函数(无参,函数体为空)
3,默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:
如果用户定义有参构造,c++不再提供无参构造,但会提供默认拷贝构造
如果用户定义拷贝构造,c++不会再提供其他构造函数

深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include<bits/stdc++.h>

using namespace std;
class Person {
public:
    Person()
    {
        cout << "Person的默认构造函数调用" << endl;
    }
    Person(int age)
    {
        m_Age = age;
        cout << "Person的有参构造函数调用" << endl;
    }
    ~Person() {
        cout << "Person的析构构造函数调用" << endl;
    }
    int m_Age;

};
void test01() {
    Person p1(18);
    cout << "p1的年龄为: " << p1.m_Age << endl;
    Person p2(p1);
cout << "p2的年龄为: " << p2.m_Age << endl;
}
int main() {
    test01();
        system("pause");
    return 0;

}

初始化列表

传统初始化赋值
#include<bits/stdc++.h>
using namespace std;
class Person {
public:
    Person(int a, int b, int c) {
        m_A = a;
        m_B = b;
        m_C = c;
    }
    int m_A;
    int m_B;
    int m_C;

};
void test01() {
    Person p(10, 20, 30);
    cout << "m_A= " << p.m_A << endl;
    cout << "m_B= "<<p.m_B<<endl;
        cout << "m_C= " << p.m_C << endl;
}
int main() {
    test01();
    system("pause");
        return 0;
}
#include<bits/stdc++.h>
using namespace std;
class Person{
public:
    //初始化列表属性
    Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {
    }
    int m_A;
    int m_B;
    int m_C;
};

void test01() {
    Person p(30, 20, 10);
    cout << "m_A= " << p.m_A << endl;
    cout << "m_B= " << p.m_B << endl;
    cout << "m_C= " << p.m_C << endl;
}
int main() {
    test01();
    system("pause");
    return 0;
}

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

符号“::”称为二元作用域解析运算符,指明了类成员的作用范围
    这里,每个构造函数和函数前的Circle::告知编译器这些函数是定义于Circle类中的(P302)

静态成员

在成员变量和成员函数加上关键字static,称为静态成员

静态成员变量
1,所有对象共享同一份数据
2,在编译阶段分配内存
3,类内声明,类外初始化
类内声明:static int m_A;
类外初始化:int Person::m_A=100;

静态成员函数
1,所有对象共享同一个函数
2,静态成员函数只能访问静态成员函数

第十章 面向对象思想

10.1 string类

可以使用以下语法来创建一个字符串:

string s ="welcome to C++";

这条语句效率不高,因为包含两个步骤:首先使用一个字符串文本开创建一个字符串对象,然后把这个字符串对象拷贝给s.
更好的方法是用使用字符串构造函数来创建字符串:

string s("welcome to C++");

也可以使用string 类的构造函数从C字符串来创建一个字符串

char s1[]="Good morning";
string s(s1);
//这里s1是一个C字符串,而s是一个字符串对象

append()

.append(s:string);         //将字符串s追加在当前字符串后
.append(s:string , index:int , n:int);    //将s中从下标index起的n个字符追加到当前字符后
.append(s:string , n:int)           //将s的前n个字符追加在当前字符串后
.append(n:int ch:char)          //将n个ch追加在当前字符串后
string s1("Welcome");
s1.append(" to C++");   
cout<<s1<<endl;         //s1 : "welcome to C++"

string s2("Welcome");
s1.append(" to C++",0,5);   //add " to C"to s2
cout<<s2<<endl;        //s2:"welcome to C"

string s3("Welcome");
s3.append(" to C++ and C",5);   //add" to C" to s3   
cout<<s3<<endl; 

string s4("Welcome");
s4.append(4,'G');          //add"GGGG" to s4;   
cout<<s4<<endl; 

字符串赋值

.assign();  //重新复制
.assign("  ");  //将原字符串替换为引号内的内容
.assign("   ",0,5)  //替换为从下标index开始的n个字符
.assign(" ",4)   //前4个字符赋值给当前字符串
.assign(4,'G')   //赋值为ch的n次重复
length(),size(),capacity()分别用来获取字符串的长度,大小和分配的存储空间的大小  //length()与size()作用相同
c_str():返回一个C字符串           //capacity()函数返回内部缓冲区的大小,该值总大于等于实际的字符串大小

字符串比较

string s1("welcome");
string s2("welcomg");
cout<<s1.compare(s2)<<endl;   //return -1;   //小于 
cout<<s2.compare(s1)<<endl; //return 1;  //大于
cout<<s2.compare(s2)<<endl;   //return 0;

获取子串

substr函数获取字符串的一个子串
string s1("Welcome");
cout<<s1.substr(0,1);      //return W;
cout<<s1.substr(3);       //返回自当前字符串从下标index开始的子串   //return come
cout<<s1.substr(3,3);        //return com     

字符串搜索

.find();
string s1("Welcome to HTML");
cout<<s1.find("co");     //return 3    //返回当前字符串字符ch出现的第一个位置
cout<<s1.find("co",6);  //从index开始ch出现的第一个位置    //return string::npos  //如果没有找到,返回string::npos,npos是string类定义的一个常量

字符串插入和替换

insert和replace函数
insert(index:int,s:string)  //将字符串s插入本字符串下标index处
insert(index:int,n:int,ch:char)       //将n个ch插入字符串下标
replace(index:int,n:int,s;string)   //将字符串从下标index开始的n个字符替换为s的内容

把数字转换为字符串

在7.11.6节中可以通过函数atoi和atof把一个字符串转换为整数及浮点数。我们可以使用itoa函数把整数转换为字符串,然而更简单的方法是使用头文件中的stringstream类。stringstream类提供的接口可使我们类似处理输入输出流一样来处理字符串。

stringstream ss;
ss<<3.1415;
string s=ss.str();

字符串分割

//如果单词由空格分割,则可以使用上面介绍的stringstream类来完成字符串分割
#include<iostream>
#include<sstream>
#include<string>
using namespace std;

int main(){
 string text("Programming is fun");
    stringstream ss(text);
    
    string word;
    while(!ss.eof()){
        ss>>word;
        cout<<word<<endl;
    }
    return 0;
    
}

//最后的输出为
//Programming
//is
//fun

//程序为字符串创建了stringstream对象,相当于构建了控制台,该对象的输入输出类似于控制台的输入输出,故可以分割以空格为分割标准的字符串 
//只读函数作为fnag

10.2 对象数组

在C++中,对象数组指的是一个数组,其中的每个元素都是一个对象。这通常意味着你有一个类(class),然后你创建了一个该类的数组。每个数组元素都是这个类的实例,拥有自己的成员变量和成员函数。

下面是一个C++中对象数组的例子:

#include <iostream>
using namespace std;

// 定义一个类
class Person {
public:
    string name;
    int age;

    // 构造函数
    Person(string n, int a) : name(n), age(a) {}

    // 打印成员信息的成员函数
    void printInfo() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

int main() {
    // 创建对象数组
    Person people[3] = {
        Person("Alice", 25),
        Person("Bob", 30),
        Person("Carol", 28)
    };

    // 遍历数组并调用每个对象的成员函数
    for (int i = 0; i < 3; ++i) {
        people[i].printInfo();
    }

    return 0;
}

在这个例子中,我们定义了一个Person类,它有两个成员变量:nameage。然后我们创建了一个Person类型的数组people,包含三个元素。每个元素都是Person类的实例,并且我们通过构造函数初始化了它们的成员变量。在main函数中,我们遍历这个数组,并调用每个对象的printInfo成员函数来打印它们的信息。

C++中的类对象数组可以存储具有复杂行为和状态的数据,并且可以像使用普通数组一样进行索引访问。

10.3 静态成员和静态函数

在C++中,类可以拥有两种特殊的成员:静态成员(static member)和静态函数(static function)。它们与类的非静态成员和函数有以下区别:

  1. 静态成员(Static Members)

    • 静态成员属于类本身,而不是类的任何特定对象或实例。
    • 这意味着静态成员在所有对象之间共享,并且可以通过类名直接访问,无需创建类的实例。
    • 静态成员可以是变量或常量,但它们不能访问非静态成员变量或函数,因为它们不与任何特定对象关联。
  2. 静态函数(Static Functions)

    • 静态函数也属于类本身,而不是类的任何特定对象。
    • 静态函数可以通过类名直接调用,不需要创建类的实例。
    • 静态函数可以访问静态成员,但不能直接访问非静态成员,因为非静态成员与类的具体实例相关联。

静态成员和函数的主要用例是当需要在所有对象之间共享数据或行为时。例如,你可能有一个记录程序运行次数的计数器,或者一个工具函数,它与类的任何特定实例无关。

下面是一个包含静态成员和静态函数的C++类的示例:

#include <iostream>
using namespace std;

class Counter {
public:
    // 静态成员变量
    static int count;

    // 构造函数
    Counter() {
        // 每次创建对象时,计数器增加
        count++;
    }

    // 静态函数,用于获取当前计数
    static int getCount() {
        return count;
    }
};

// 定义静态成员变量的初始值
int Counter::count = 0;

int main() {
    // 创建Counter类的两个对象
    Counter c1;
    Counter c2;

    // 直接通过类名访问静态成员函数
    cout << "Count: " << Counter::getCount() << endl; // 输出2

    return 0;
}

在这个例子中,Counter类有一个静态成员变量count,用于跟踪创建的Counter对象的数量。还有一个静态函数getCount,用于返回当前的计数。由于count是静态的,它在所有Counter对象之间共享,并且可以通过类名直接访问。静态函数getCount可以通过类名直接调用,而不需要创建类的实例。

10.3 对象合成

如果一个对象仅被一个聚合对象所有,则两个对象之间的关系成为合成关系

第十一章 指针及动态内存管理

11.1 指针基础

//内存中的每一个字节都有一个唯一的地址,一个变量的地址就是分配给该变量第一个字节的地址

//声明一个指针变量
int* pCount;
//将变量count的地址赋予指针pCount
pCount=&count;    //正确的向指针赋予地址的语法
//当&符号放在一个变量前时,称为地址运算符,返回变量的地址

//也可以指针声明的时候进行赋值
int* pCount=&count;

//区分典例
#include<iostream>
using namespace std;
int main(){
    int count =5;
    int* pCount=&count;
    cout<<"The value of count is "<<count<<endl;
    cout<<"The address of count is "<<&count<<endl;
    cout<<"The address of count is "<<pCount<<endl;
    cout<<"The value of count is "<<*pCount<<endl;
    
    return 0;
}

/*
输出为:
The value of count is 5
The address of count is 0000003CFDF4FCF4
The address of count is 0000003CFDF4FCF4
The value of count is 5
*/
通过指针引用一个变量称为间接引用
*称为间接引用运算符或解引用运算符 

//注:
在int* pCount=&count 中*号用于声明指针变量
而在*pCount 中*的作用是 解引用

用typedef定义同义类型

typedef int integer
//之后可以用integer来声明int型变量
//typedef创建了一个已知数据类型的同于名字

常量指针:

常量指针指向一个不变的内存位置,但该内存位置处的实际值可以发生变化

1,结构体struct的用法

#include<iostream>
using namespace std;

//struct  名字 {包含的类型的变量};

//typedef  给类型起别名

typedef struct student{
	string name;
    int age;
    string hobby;

} Stu1;   //用typedef给自定义的类型起别名:Stu1

typedef student Stu2;
int main(){

    student zhangsan;  //可以用类型的名字定义变量
    Stu1 lisi;  //也可以用别名来定义变量
    Stu2 wangwu;
    
    //变量.成员 调用
    

}

2,结构体指针

int a,*p;//指针变量
p=&a;
*p=123;  //*是间接引用运算符

//指向结构体变量的指针
struct student{
char num[11];
char name[21];
int age;
float score;

};
struct student st,*p;  //定义了一个结构体变量st,和指针变量p
//在C++中,由于支持对结构体的隐式命名空间,可以省略”struct“关键字
//p就是一个指向结构体变量的指针变量
p=&st;


gets((*p).name);
scanf("%f",&(*p).name);

//"->"称为指向运算符
//   一般形式:结构体指针变量->成员名
//等价于(*结构体指针变量).成员名

gets(p->num);  //等同于gets((*p).num);
scanf("%d",&p->num);

11.2 数组

在C++中,数组名实际上是指向数组中第一个元素的常量指针
如果在数组变量后不用方括号和下标,它实际上表示数组的起始地址
int list[6]={0,1,2,3,4,5,6};
cout<<"The starting address of the array is "<<list<<endl;
所以可以通过解引用的方式来访问数组中的元素
可用*list 访问第一个元素,*(list+1)访问第二个元素   //实际情况中是首地址+sizeof(int) 

11.3 动态持久内存分配

new操作符可以在运行时为基本数据类型,数组和对象分配持久的内存空间
声明如下:
int* p=new int(4);
//该指令为一个int型变量分配内存空间,并在运行时初始化为4,int型变量的地址赋予指针p
//也可以动态的创建一个数组
int size;
cin>>size;
int* list=new int[size];
//使用new操作符创建的数组称为动态数组,不同于普通数组,动态数组的大小可以是一个变量

在C++中,局部变量在栈中分配空间,而由new操作符分配的内存空间则出自于称为自由存储区域或者堆的内存区域。
分配的内存空间一直是可用的。直至显示地释放它或程序终止

显示释放,使用delete操作符,
delete p;  //释放变量
delete [] list;  //释放数组

当一个指针指向地内存被释放后,该指针的值就是未定义的。这些未定义的指针称为悬空指针。
不能再悬空指针应用解引用运算符(*),这样做可能会导致严重后果
delete只能用于指向new操作符创建的内存的地址,否则会导致不可预料的问题

罗列一个错误的代码:
//再释放一个指针指向的内存空间前,可能无意为它赋予了新的地址,代码如下:
int* p=new int;
*p=45;
p=new int;
//第三行中将一个新的地址赋予p,导致保存45的初始内存空间将无法再访问,因为已经没有任何指针指向它。这段指针无法访问也无法释放,这就是所谓的内存泄露
//为养成好的习惯,每个new操作都应该有相应的delete操作

11.4 创建及访问动态对象

调用对象的构造函数可以动态地创建对象
ClassName* pObject =new ClassName;   //使用无参构造函数创建一个对象,并将对象地址赋予指针
ClassName* pObject =new ClassName(arguments);     //带参数地构造函数创建对象

string* p=new string("abcdef");
cout<<(*p).substr(0,3)<<" "<<(*p).length();

//C++提供了一个成员选择操作符,可以简化通过指针来访问对象成员。该操作符称为箭头操作符(->)
cout<<p->substr(0,3)<<" "<<p->length();

当程序结束时,对象会被销毁,也可使用delete显示销毁对象

11.5 this指针

类内的成员变量和成员函数分开存储
只有非静态成员变量才属于类的对象上

C++编译器会给每个空对象也分配一个内存空间,是为了区分空对象占内存的位置

this指针

this指针指向被调用的成员函数所属的对象(this指针指向被调用对象本身)
(成员函数隐含this指针)

this指针是隐含每一个非静态成员函数内的一种指针

this指针的本质是 指针常量 指针的指向是不可以被修改的

用途:
当形参和成员变量同名是,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this

构造函数中名为radius的参数是一个局部变量,它屏蔽了对象中的数据与radius,为了能够引用radius的数据域,需要使用this->radius(等价于((*this).radius)

3,->成员访问运算符

在C++和C#等编程语言中,->是一个成员访问运算符,通常用于访问类或结构体指针所指向对象的成员。这个运算符使得我们可以通过指针来访问对象的成员函数和成员变量,而不必先解引用指针再使用.来访问。

具体来说,如果有一个指向类或结构体对象的指针,我们可以使用->运算符来访问这个对象的成员。例如:

struct MyClass {
    int value;
    void print() {
        std::cout << "Value: " << value << std::endl;
    }
};

MyClass obj;
MyClass* ptr = &obj;

// 使用`.`来访问对象成员
obj.value = 10;             
obj.print();

// 使用`->`来访问对象成员
ptr->value = 20;
ptr->print();

空指针访问成员函数

空指针也是可以访问成员函数的,但也要注意有没有使用this指针,用到this指针,需要加以判断保证代码的健壮性


    
if(this==NULL){
return ;
}

用到this指针,则不能用空指针来访问成员函数

const修饰成员函数


成员函数加上const后叫常函数
1,常函数内不可修改成员属性
2,成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象
1,常对象只能调用常函数

this指针是指针常量
相当于:Person* const this
在成员函数后加const,本质指向的是this指针,让指针指向的值也不可以修改
形式:void showpoint()const{}
相当于const Person * const this



mutable int m_B;

避免多次包含

“包含保护”可避免头文件被多次包含
模板:
#ifndef ClassName_H  //如果没有被定义,巴拉巴拉,否则,头文件剩下的部分将被忽略
#define ClassName_H

…………

#endif  指出头文件的结束  

第十二章 模板,向量和栈

12.1 模板基础

如果相求两个整数,浮点数,字符,字符串中较大的,
若使用重载函数,需要写四个,所以不如写一个具有通用类型的函数
模板前缀:
template<typename T>;
此处的T是类型参数,习惯用T来表示
 
template<typename T>
T maxValue(T value1,T value2){
if (value1>value2)
return value1;
else 
return value2;
}
maxValue(1.0,1.5);
特别的,比较string类时,应该这么写:
maxValue(string("NBC"),string("ABC"));
而不能写成这样maxValue(“NBC”,“ABC”),因为“NBC”与“ABC”时C字符串,此时比较的是“NBC”和“ABC”的地址

12.2 模板类

语法与模板寒素类似,都需要在类声明前加上模板前缀
template<typename T>

12.3 vector向量类

vector定义二维数组,//一个四行三列的数组
vector<vector<int> >matrix(4);
for(int i=0;i<4;i++){
matrix[i]=vector<int>(3);
}
//vector<vector<int> >matrix(4);   两个>>中间有个空格,如果没有空格一些编译器可能会报错

第十三章 文件输入输出

13.1 文本输入输出

类ifstream用于从文件中读数据,类ofstream用于向文件中写数据,而类fstream用于既读又写的情况
绝对文件名包含一个文件的名字及其完整的路径和驱动器符
实现代码见4.11
检测文件是否存在
可以在调用open函数后,立即使用fail函数来进行检测
input.open("scores.txt");
if(input.fail()){
cout<<"File does not exist"<<endl;
}

检测文件结束
使用eof()函数
while(!input.eof()){

}

让用户输入文件名
string filename;
cin>>filename;
ifstream input(filename.c_str());
//此程序提示用户输入用户名,存储类型为string型,由于在标准C++中,传递给输入输出流构造函数或者open函数的文件名必须是C字符串,需要用string类的c_str()函数进行转换,将string对象转换为C字符串

13.2 函数getline,get和put

函数getline可用来读包含空格的字符串,函数get/put可用来读写单个字符
从文件中读取字符串
getline(ifstream& input,int string s,char delimitChar);
常用形式是getline(cin,s);   默认第三个参数delimitChar的缺省值为("\n")
getline(input,city,'#')  //从文件中读取字符,重复此操作直到遇到字符#

get函数和put函数见p432 13-7

13.3 fstream和文件打开模式

如果程序需要使用同一个流对象既进行输入又进行输出,那么使用fdtream是最方便的。
为用fstream对象打开一个文件,必须指定文件打开模式 ,告知C++如何使用文件      
模式描述
ios::in打开一个文件用于输入(以输入模式打开文件)(当使用ios::in标志来打开文件时,它告诉fstream类,你打算从文件中读取数据)
ios::out打开一个文件用于输出(如果文件已存在,则会截断文件内容)
ios::app所有输出数据追加于文件末尾
ios::ate打开一个输出文件。如果文件已存在,移动到文件末尾。数据可写入文件任何位置
ios::trune如果文件已存在,丢弃文件内容(这实际上是ios::out的缺省方式)
ios::binary打开一个文件用于二进制输入输出
可以用“|”运算符组合使用多个模式。“|”是位或运算符
例如:为了打开一个名为city.txt的输出文件用于附加数据,可使用以下语句:
stream.open("city.txt",ios::out|ios::app);
//典例
#include<iostream>
#include<fstream>
#include<string>
using namespace std;

int main(){
fstream inout;
//创建一个文件
inout.open("city.txt",ios::out);
//写文件
inout<<"Dallas"<<" "<<"Houston"<<" "<<"Atlanta"<<" ";
inout.close();
//关闭文件后重新打开
//添加数据到文件末尾
inout.open("city.txt",ios::out|ios::app);
//写文件
inout<<"Savannah"<<" "<<"Austin"<<" "<<"Chicago";
inout.close();

string city;

inout.open("city.txt",ios::in);
while(!inout.eof()){
inout>>city;
cout<<city<<" ";
}
inout.close();
return 0;
}
//最后的输出结果
//Dallas Houston Atlanta Savannah Austin Chicago

13.4 检测流状态

img

13.5 二进制输入输出

可以使用“<<”运算符和put函数将数据写入文本文件,使用“>>”运算符,get和getline函数从文本文件读取数据。为了读/写二进制文件,必须对流对象使用read和write函数

以二进制形式打开文件参数 :

ios::binary : 以二进制方式打开文件 ; 默认是 ASCII 码方式打开 ;
ios::in | ios::binary : 以 二进制形式 打开输入文件 ;
ios::out | ios::binary : 以 二进制形式 打开输出文件 ;
ios::in l ios::out I ios::binary : 以 二进制形式 打开 输入 和 输出 文件 ;

Write函数

//函数语法如下:
streamObject.write(const char* s,int size)
//写入类型需为字符数组
string s="Atlanta";
binaryio.write(s.c_str(),s.size());

//常常需要向文件中写入非字符数据,可以通过reinterpret_cast运算符来实现此目的
//此运算符可以将一个指针类型转换为与其不相关的指针类型,只是简单地进行了指针值的二进制复制并不改变指针指向的数据
reinterpret_cast<dataType*>(address)

int value=199;
binaryio.write(reinterpret_cast<char*>(&value),sizeof(value));

Read函数

//函数语法如下:
streamObject.read(char* address,int size);
//参数size指示可以读取的最大字节数,实际读取的字节数可从成员函数gcount获取
//使用read函数读取字符
#include<iostream>
#include<fstream>
using namespace std;
int main(){
fstream binaryio("city.dat",ios::in|ios::binary);
char s[10];
binaryio.read(s,10);
cout<<"Number of chars read: "<<binaryio.gcount()<<endl;
s[binaryio.gcount()]='\0';   //添加终止符
cout<< s<<endl;
binaryio.close();
return 0;
}
//程序输出为:
//Number of chars read: 
//Atlanta

//调用gcount函数获取实际读取的字符数

//对于非字符串
int value=199;
binaryio.read(reinterpreter_cast<char*>(&value),sizeof(value));

13.6 随机访问文件

随机访问文件可用seekg()和seekp()移动文件指针到任意位置

seekp(seek put)用于输出流,seekg(seek get)用于输入流

两个函数各有两种应用方式
第一种是一个参数,参数指的是绝对位置,如:
input.seekg(0);
output.seekp(0);
另一种是两个参数:第一个参数是长整型,指出偏移量,第二个参数成为定位基址,指出偏移量是相对于那个位置

定位基址描述
ios::beg偏移量相对于文件开始位置
ios::end偏移量相对于文件结尾位置
ios::cur偏移量相对于文件指针当前位置

seekp(100,ios::beg); //将文件指针移动到从文件开始第100个字节处
seekg(-100,ios::beg); //将文件指针移动到文件末尾向后100个字节处

可以使用tellp和tellg函数返回文件指针的当前位置

第十四章 运算符重载

14.1 运算符重载

运算符重载不能改变运算符的的优先级和结合律。

(1)运算符都是有默认的含义的,比如"+“符号,表示两个对象的相加,对于像int、long等C++原生的类型,”+“符号可以很好的处理,因为这是明确且不变的;但是”+“进行相加的对象时用户自定义的,那默认的”+“在处理时就会出现问题,因为编译器根本不知道用户会如何定义类,更不知道怎么对两个对象进行相加的操作,这时候就需要定义类的程序员去将”+“号进行重载,重新定义针对类的”+“运算符操作;
(2)程序中操作都是以函数为单位的,运算符能进行操作实质也是编译器帮我们把运算符和某个函数进行绑定,当我们使用”+"运算符时,编译器会自动帮我们调用默认的相加函数;
(3)编译器提供的运算符默认函数,只能进行能提前确定的一些操作,基本上就是对C++原生自带类的操作,当涉及用户自定义的类操作时,就会出现问题,这时候就需要定义类的程序员自己去重新定义类的运算符,也就是运算符重载;
(4)当程序执行时,会去匹配运算符绑定的函数,类似于函数重载,如果没有对运算符进行重载就调用默认的运算符函数,如果匹配上重载的运算符函数就调用重载的函数;

                版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_42031299/article/details/127593164

​ 运算符重载的实现,鼠鼠就不再这里放了(允许我偷个懒),见书P458-P464页,大致思路就是编写了一个函数实现重载的功能

14.2 友元函数和友元类

友元的目的是让一个函数或者类访问另一个类中的私有成员
关键字为:friend

三种实现方式:
1,全局函数为友元
2. 类做友元
3,类中的成员函数做友元 

这一章看下来感觉挺迷的,然后前后也没时间细学了,所以粗略的看了看,以后遇到了再学吧。

第十五章 继承和多态

面向对象程序设计允许从已有类派生出新的类,这称为继承。
继承允许声明一个通用类(例如,基类),并随后将其扩展为更专用的类(例如,派生类)
一个派生类(子类)继承了所有可访问的数据域和函数,同时可以增加新的数据域和函数

相关概念的进一步理解及代码可以参考csdn上诸多大神,鼠鼠真的要赶时间了(qaq)

泛型程序设计:当程序中需要一个基类对象时,向其提供一个派生类对象是允许的。

派生类构造函数调用自身代码前先调用基类的构造函数。
之后派生类先调用自身的析构函数,再调用基类的析构函数

函数重定义:在基类中定义的函数能够在派生类中被重新定义。

多态:

多态意味着一个超类型的变量可以引用一个子类型的对象
一个类定义了一种类型,被派生类定义的类型叫做子类型,被基类定义的类型叫做超类型
(多态通俗来讲是当不同的对象去完成时会产生出不同的状态)

在继承中要构成多态还有两个条件:
1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

一个函数可以在沿着继承关系链的多个类中实现。虚函数使得系统能够基于对象的实际类型决定在运行时调用哪一个函数

一个类的保护成员protected可以被派生类所访问4

类型转换:static_cast和dynamic_cast

dynamic_cast运算符能够在运行的时候将一个对象强制转换城其实际类型
dynamic_cast可以在运行时检查强制转换是否成功,static_cast则在编译时起作用
dynamic_cast只在多态类型(有虚函数的类型)上起作用

//总之,这章中的很多概念还有待进一步学习和理解,如:虚函数,纯虚函数,动态绑定等概念

第十六章 异常处理

16.1 异常处理概述

//异常是用一个throw语句抛出,同时用一个try-catch块来捕获
#include<iostream>
using namespace std;
int main(){
cout<<"Enter two integers: ";
int number1,number2;
cin>>number1>>number2;

try{
if(number2==0)
throw number1;
cout<<number1<<" / "<<number2<<" is "<<(number1/number2) <<endl; 
}
catch (int ex){
cout<<"Exception: an integer "<<ex<<" cannot be divided by zero"<<endl;
}
cout<<"Exception continues ..."<<endl;
return 0;
}
//程序输出:
Enter two integers: 5 3
5 / 3 is 1
Exception continues ...

Enter two integers: 5 0
Exception: an integer 5cannot be divided by zero
Exception continues ...
//这段代码模拟了异常出现背后的机理

//此程序包含一个try块和一个catch块。try块包含在正常情况下应执行的代码,catch块包含当number2为0时应当执行的代码
当number2为0时,程序抛出一个异常(值)时,异常被catch块捕获,跳转到catch中的代码语句所以程序不会再执行原代码处后面的代码

//如果对异常内容不感兴趣,可以忽略catch块参数,例如:
try{

}
catch(int){
cout<<"Error occurred "<<endl;

} 
//异常类的概念:
多重异常捕获:
一个try-catch模块可能包含多个catch语句,用于处理try语句抛出的各种异常类型

重抛出异常:
异常类型列表

第三部分 算法和数据结构

第十七章 递归

选择排序

二分搜素

尾递归:当一个递归函数在返回递归调用后没有待执行的操作时,这种递归函数称为尾递归。(即递归结束后,函数也结束了)
可以通过优化尾递归来减少堆栈空间

数据结构的初步讲解这一张还是很简单的,就是一个递归的思想吧,倒是没设么可记录的地方。我认为比较好的方法是想出解决一道题的思路,然后一步步尝试翻译为代码,遇到不知道怎么实现的就到网上查,这样的话一道题真的收获很大,最后还可以捞一眼题解的代码,看看他们方法的简便性,以及一些代码的细节。

附到最后的小彩蛋:

1,(大哭)某天在写要点时,库库学了一个多小时,然后有事走开了,书压着键盘把软件压卡了,然后辛辛苦苦写的笔记没了,又重新码了一遍(哭),于是我养成了学完一章or离开电脑先保存的习惯

2,(哈哈哈哈哈)在黑皮书的439页程序清单13-12中它的程序输出第一个字母是大写,哇哈哈,鬼知道无聊透顶的我发现了这个微不足道的错误有多激动(hh)(已经疯了)

3,蒟蒻于24年8月8日0点27分正式看完这本书,容我发癫一下(傻乐)。至于学到多少就不知道了。预计接下来5天看看《Python编程:从入门到实践》,想当年还是up主林粒粒的随书视频极大激励了我的学习兴致,但还是没有坚持看完(小丑)。看完Python再准备恶补恶补机组,估计得看两个多月,但假期真没几天了(哭)。

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值