C++ primer plus 笔记(持续更新)

第4章 复合类型

P74 4.2 字符串

反斜杠零的用处

C风格字符串在遇到‘\0’,会结束输出。因此可以有以下用法:

#include<iostream>
#include<cstring>

int main() {
	using namespace std;
	const int Size = 15;
	char name1[Size];
	char name2[Size] = "C++owboy";
	cout << "Hello,I'm" << name2 << "!What's your name";
	cin >> name1;
	cout << "你的名字有" << strlen(name1) << "个字符" << endl;
	name2[3] = '\0';	//注意这里的一个技巧通过\0来提前终止
	cout << "前三个字母是" << name2;
}

get()

get()同getline()不同,get不再读取并丢弃换行符而是将换行符留在输入队列中。因此假设我们连续两次调用get

cin.get(name,ArSize);
cin.get(name2,ArSize);

这种情况下,第二个name2则没有读取到任何内容。幸运的是cin.get()在没有任何参数的情况下可以读取下一个字符进行处理,也就是可以将换行符读取掉,为下次输入做准备。

cin.get(name,ArSize);
cin.get();
cin.get(name2,ArSize);

因为cin.get()返回的是一个cin对象,所以可以直接在后面将get()拼接起来使用:
cin.get(name,ArSize).get()

P84 4.3 字符串

C风格字符串的特殊操作

使用strcpy()将字符串复制到字符数组中
使用strcat()将字符串附加到字符串末尾
如果使用string操作字符串:str3 = str1 + str2;
而使用C风格操作字符串时有:

strcpy(charr3,charr1);
strcat(charr3,charr2);

注意:未初始化的字符串数组,使用strlen()计算长度将会是不确定的,因为其将会从数组第一个元素计算起直到遇到空字符

其它形式的字符串字面值

之前我们学习过wchar_t char16_t char32_t多种特殊类型,C++分别使用前缀L、u和U表示:

wchar_t tittle[] = L"这个是串中文字符";
char16_t name[] = u"随便输的";
char32_t car[] = U"这个也是随便输的";

C++还使用了原视字符串。在原始字符串中,字符串就是字符串本身,例如:‘\n’将不再表示换行。原始字符串用“(和)”用作定界符,如:
cout << R"(Jim "king" \n dajdkal)"
这将会直接输出:Jim “king” \n dajdkal
如果需要显示()呢?使用 R"+(标识原始字符串的开头,用)+"标识字符串的结尾。

P109 数组的地址

对数组取地址时,数组名不会被解释为地址。数组名被解释为第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:

short tell[10];
cout << tell << endl;
cout << &tell << endl;

从数字上说这两个地址相同,但在概念上,&tell[0] 表示的是一个两字节的内存块地址,而&tell表示的是一段有20字节内存块的地址。因此对二者加一之后将会有所不同。您可能会这样子声明一个数组
short (*pas)[20] = &tell;
如果省略括号将会导致pas先和[20]结合,导致pas是一个指针数组。由于pas被设置为&tell固(*pas)[0]为tell数组的第一个元素。

第5章 循环和关系表达式

P131 一个阶乘的思路

#include<iostream>

using namespace std;

const int ArSize = 16;
int main() {
	long long a[ArSize];
	a[1] = a[0] = 1LL;
	for (int i = 2; i < ArSize; i++)
		a[i] = i * a[i - 1];
	for (int i = 0; i < ArSize; i++)
		cout << a[i] << endl;
	return 0;
}

P135 自增自减运算符注意点

注意自增自减运算符在执行了完整表达式之后才会执行如下面语句:

#include<iostream>

using namespace std;

int main() {
	int x = 1;
	int y = (4 + x++) + (6 + x++);
	cout << y << endl;
}

4 + x++不是一个完整的表达式,因此C++不保证计算完4 + x++后立即增加1.在这段代码中,整条赋值语句是一个完整表达式,而分号标识了顺序点,因此只保证执行到下一条语句前x被自增两次。
##语法技巧-逗号运算符
在for循环更新中使用逗号运算符:
for( j = 0, i = 1; j<i ; --j ,--i
在所有运算符中,逗号运算符是最低的,如下面的语句:
cat = 17,212 等价于 (cat = 17),212后面的212没有起到作用而被抛弃。

P143 C风格字符串的比较

strcmp()接受两个字符串地址作为参数。若两字符串相同则返回0,若第一个字符串按字母表顺序在第二个字符串之前,则返回一个负数值,反之则反。
下面这段代码通过循环修改首字母,直到其与“mate”相同为止。

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

int main() {
	char word[5] = "?ate";
	for (char ch = 'a'; strcmp(word, "mate"); ch++) {
		cout << word << endl;
		word[0] = ch;
	}
	cout << "完成替换查找";
}

编写延时循环

long wait = 0;
	while (wait < 1000)
		wait++;

这种方法的问题就是不同的机器有不同的处理速度,所以进行移植时就要修改计数限制。C++中给出了解决方案,定义符号常量——CLOCKS_PER_SEC,该常量等于每秒钟包含的系统时间单位数。因此将clock()获取到的程序执行开始的系统时间数除以单位数就可以得到秒数。clock_t作为clock()返回的类型,如下有个使用示例:

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

int main() {
	cout << "输入一个时间延迟" << endl;
	float secs;
	cin >> secs;
	clock_t delay = secs * CLOCKS_PER_SEC;
	cout << "strating\a\n";
	clock_t start = clock();
	while (clock() - start < delay)
		;
	cout << "结束" << endl;
	return 0;
}

P149 类型别名

注意typedef是类型别名,其本身便是一种类型,不能再添加long short 等修饰。

P152 循环和文本输入

使用原始的cin进行输入

直接上代码示例:

#include<iostream>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "请输入字符以#结束:" << endl;
	cin >> ch;
	while (ch != '#') {
		cout << ch;
		++count;
		cin >> ch;
	}
	cout << endl << count << "个字母读入\n";
	return 0;
}

这段代码随便输入了几个运行结果如下:
![cin 判断输入][1]
程序在循环之前就读取了第一个字符,这样循环可以测试第一个字符。注意这很重要,因为第一个字符可能就是#,在这种情况下可以跳过整个循环。在使用cin时将会主动忽略在输入时的空格和换行符,在输入过程中发送给cin的输入被缓冲,这意味着只有在用户按下回车键后输入的内容才会被发送给程序。

使用cin.get(char)进行补救

使用cin.get()函数可以补救直接使用cin的部分不足:

#include<iostream>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "请输入字符以#结束:" << endl;
	cin.get(ch);
	while (ch != '#') {
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count << "个字母读入\n";
	return 0;
}

运行结果如下:
![cin.get()运行结果][2]
从图中我们可以看出来cin.get()函数并没有忽略空格。

文件尾条件

检测文件尾EOF,Win环境下可以通过按Ctrl+Z和Enter来模拟文件尾条件(不同系统可能会不一样)。检测到EOF后,cin会将两位(eofbit和failbit)都设置为1,可以通过成员函数eof()来查看。若检测到EOF,则cin.eof()将返回bool值true。同样,若eofbit和failbit被设置为1,则fail()成员函数将返回true,反之false。由于eof()和fail()均报告读取后的结果,所以应将二者放在读取后。

#include<iostream>
using namespace std;

int main() {
	char ch;
	int count = 0;
	cout << "请输入字符以#结束:" << endl;
	cin.get(ch);
	while (cin.fail()==false) {
		cout << ch;
		++count;
		cin.get(ch);
	}
	cout << endl << count << "个字母读入\n";
	return 0;
}
  • 注意在使用EOF标记后,cin将不再读取输入,这对于文件来说是很合理的因为一个文件的尾之后一定不会再有内容了,但在输入的过程中可能会还有,这时可以在之后使用cin.clear()清除EOF标记,使输入继续进行。
  • cin.get(char)的返回值是一个cin对象,但是在需要其发生转换的地方时会自动转换为bool类型(如在while循环中):while(cin)

文件尾EOF其实相当于-1,如果使用cin.get()(没有参数)并测试EOF,则必须将返回值赋给int型变量,而不是char,之后再将其强制转换为char类型:

#include<iostream>
using namespace std;

int main() {
	int ch;
	int count = 0;
	cout << "请输入字符以:" << endl;
	while ((ch = cin.get())!=EOF) {
		cout.put(char(ch));
		++count;
	}
	cout << endl << count << "个字母读入\n";
	return 0;
}

第6章 分支语句和逻辑运算符

P177 字符函数库cctype

在这个函数库中,有判断是否为字母、数字、标点符号的函数。如:若ch是一个字母,则isalpha(ch)函数将返回一个非零值,否则返回0.类似的还有很多。

P195 读取文本文件

  • is_open()函数可以判断文件是否被打开,成功打开将返回true。

文件尾的判断

eof()只能判断是否达到EOF,而fail()可以用于检测EOF和类型不匹配,请看下面代码段:

if (inFile.eof())
		cout << "文件已经结束";
	else if (inFile.fail())
		cout << "读取数据不正确";
	else
		cout << "未知错误";

这样就可以对不同的错误进行处理。P197页有关于good()的描述。

第7章 函数 C++的编程模块

使用交替乘除可防止中间操作数超过最大浮点数。

一则关于函数以数组为参的注意

请看如下代码:

#include<iostream>
using namespace std;

int test(int a[]) {
	int c = sizeof(a);
	return c;
}

int main() {
	int a[3] = { 1,2,3 };
	int b = test(a);
	cout << sizeof(a)<<endl;
	cout << b;
}

这段代码输出如下所示:
![数组注意事项][3]
为什么不一样呢?因为在函数中传递的是数组的首元素地址,而在test函数中也是以指针的形式来操作数组的,要理解数组传递时的指针特性十分重要。

数组的区间处理

在理解了数组以指针传递的特性后,仍以上述示例为前提可以采用如下方式对数组进行分段处理:sum (a,a+3);然后在sum函数内部利用指针的运算进行操作。

const的说明

const int * a = &b指向的内容不可变,但指针指向可以变化
int *const a = &bb内容可以边,但指针指向b这一内容不可变化
啊 这段比较重要 但也不想打了 直接页码P222

P224 函数和二维数组

传递二维数组时需指出第二个的下标数:

int sum(int ar2[][4], int size) {

	}

函数和C风格字符串

#include<iostream>
using namespace std;

unsigned int c_in_str(const char* str, char ch);

int main() {
	char mmm[15] = "minimum";
	const char *wail = "ululate";
	unsigned int ms = c_in_str(mmm, 'm');
	unsigned int us = c_in_str(wail, 'u');
	cout << "mmm中m的数量为:" << ms << endl;
	cout << "wail中us的数量为:" << us << endl;
}

unsigned int c_in_str(const char* str, char ch)
{
	unsigned int count = 0;
	while (*str) //注意这里的应用
	{
		if (*str == ch)
			count++;
		str++;
	}
	return count;
}

最后的空字符作为循环结束的条件。

P242 函数指针

声明函数指针,double (*pf)(int),在使用函数指针时,pf与(*pf)是等价的,如可以这样调用函数(*pf)(5)、pf(5)。这样都是可以的。在使用指针层级较多时,可以使用auto关键字来自动判断变量类型
如果要声明一个包含三个函数指针的数组呢?可以使用这种方法:const double * (*pa[3])(const double *, int)={f1,f2,f3},在这时便不能使用auto来自动判断,auto只能用于单值初始化,而不能用于初始化列表。但在声明了pa之后再声明同样的数组就简单了:ayto pb = pa

第8章 函数探幽

P263 临时变量、引用参数和const

如果出现有实参与引用参数不匹配的情况,C++将会生成临时变量。当前,仅当参数为const引用时,C++才会允许这样做。P263页看不太懂,自己实操了一下也不行。

P267 返回引用时需要注意的问题

这里在函数内部声明了指针并返回了之后,容易造成错误:

const free_throws & clone(free_throws & ft) {
	free_throw *pt;
	*pt = ft;
	return *pt;
}

对于这个函数,其实是隐藏了对new的调用,之后应该有delete来进行内存的释放。

P275 默认参数

对于带有默认参数的函数,必须 从右向左添加默认值。也就是说,要为某个参数设置默认值,则必须为它右边所有的参数提供默认值。这里给出一个书上的示例,其循环终止条件应当注意

#include<iostream>
using namespace std;

const int ArSize = 80;
char * left(const char* str, int n = 1);
int main() {
	char sample[ArSize];
	cout << "输入一串字符\n";
	cin.get(sample, ArSize);
	char* ps = left(sample, 4);
	cout << ps << endl;
	delete[] ps;
	ps = left(sample);
	cout << ps << endl;
	delete[] ps;
	return 0;
}

char * left(const char* str, int n) {
	if (n < 0)
		n = 0;
	char *p = new char[n + 1];
	int i;
	for (i = 0; i < n&&str[i]; i++)
		p[i] = str[i];
	while (i <= n)
		p[i++] = '\0';
	return p;
}

P276 函数重载

在有多个函数重载时,编译器对匹配函数的依据是“特征标”,即函数()内的形参类型,来进行匹配。

重载引用函数

请看下面三个函数原型:

void sink(double & r1);
void sink(const double & r2);
void sink(double && r3);

左值引用参数r1与可修改的左值参数(如double变量)匹配;const左值引用参数r2与可修改的左值参数、const左值参数和右值参数(如两个double的和)匹配;最后右值引用r3与右值匹配。这可能会引起冲突,因此,编译器将调用最匹配版本。

double x = 55.5;
const double y = 321.2;
sink(x);//将匹配r1
sink(y);//将匹配r2
sink(x+y);//匹配r3
//若没有定义r3,则sink(x+y)匹配r2

P281 函数模板

函数模板同样可以根据函数参数的不同来进行重载。如有以下两个函数原型:void swap(T &a, T&b)void swap(T a[], T b[], int a

显示具体化

由于函数模板对于自定义结构体的局限性,我们可以显示得将函数具体化。如有如下结构体:

struct job{
    char name[40];
    double salary;
    int floor;
}

此时如果使用原本得swap函数就显得不太合适了。使用单独的函数声明:template <> void swap<job>(job &a, job &b),就可以为job设置单独得处理函数了。

template <class T>
void swap(T &a, T &b) {
	//代码块
}

template <> void swap<job>(job &a, job &b) {
	//代码块
}

模板实例化

实例化的意思我理解的就是,仍然共用一套函数模板代码,但是在其代码块部分使用显示得声明类型,在这一过程中可能会出现有类型转换。如有以下函数:

template <class T>
T add(T a, T b) {
	return a + b;
}
//代码块
int m = 6;
double x = 10.2;
cout << add<double>(x, m) << endl;

在这个过程中可以将m强制转换成double类型,并使用double类型实例化add。

模板具体化优先级

编译器在进行模板替换时,必须明白那个函数是最佳的,因此遵循有以下从最优到最差的原则:

  1. 完全匹配,但常规函数优于模板。
  2. 提升转换(例如,char和short自动转换为int,float自动转换为double。
  3. 标准转换(例如,int转换为char,long转换为double)。
  4. 用户定义的转换,如类中声明的转换
    在匹配时const优先匹配特征标中含有const的模板。什么叫做更具体呢?来看这段代码示例:
template <class T> void recycle(T t); //#1
template <class T> void recycle(T* t); //#2

struct blot
{
	int a;
	char b[];
};
blot ink = { 24,"3adw" };
recycle(&ink);

recycle(&ink)调用与#1模板匹配时将T解释为blot*;与#2匹配时将T解释为blot。相对于#2,#1有更多的一步解引用,因而在这个情况下#2将更加具体。下面通过一个完整实例来加深这一印象:

#include<iostream>
using namespace std;

template <class T> //#1
void show(T a[], int n) {
	cout << "#1输出为:\n";
	for (int i = 0; i < n; i++) {
		cout << a[i] << ' ';
	}
	cout << endl;
}

template <class T> //#2
void show(T *a[], int n) {
	cout << "#2输出为:\n";
	for (int i = 0; i < n; i++) {
		cout << *a[i] << ' ';
	}
	cout << endl;
}

int main() {
	int a[6] = { 1,2,3,4,5,6 };
	int *b[6];
	for (int i = 0; i < 6; i++) {
		b[i] = &a[i];
	}
	show(a,6);
	show(b,6);
}

下面是输出结果:
![输出][4]
在处理数组b时,#2将更加具体,因为它做出了特定假设:数组内容是指针。因此被使用。如果去掉#2,将会调用#1,输出将是a中元素的地址。

手动选择

如果在代码中既含有模板函数又含有非模板函数,则可通过显式实例化来强制代码选择模板函数:cout<<lessser<>(m,n),lessser<>(m,n)中的<>指出了编译器应选择模板函数。

关键字decltype

template<class T1,class T2>
void ft(T1 x, T2 y) {
	? type ? xpy = x + y;
}

在这种情况下,x+y应该是什么类型呢?decltype关键字给出了解决方案:

decltype(x+y) xpy;
xpy = x+y;

其含义是x+y的类型来定义xpy,上述代码也可简写为decltype(x+y) xpy = x + y,下面我们来详细理解下这个关键字:
假设有如下声明:
decltype(expression) var

  1. 如果expression是一个没有用括号括起的标识符,则var的类型与该标识符类型相同,包括const等限定符:
double x = 5.5;
double y = 7.7;
double &rx = x;
const double *pd;
decltype(x) w; //w是double类型
decltype(rx) u = y; //u是double &类型
decltype(pd) v; //v是const double *类型
  1. 如果括号内的内容是个函数,那么其类型将于括号内函数的返回值类型相同,注意这里并不会运行函数,而是直接通过函数返回值来确定类型。
  2. 如果expression是一个左值,那么var为指向其类型的引用。这意味着好像前面的 w 应该是引用类型,但注意这种情况已经在第一步处理过了。要进入第三步,expression必须是用括号括起来的标识符。如下所示:
double x = 5.5;
decltype((x)) r2 = x; //r2是一个double &类型
decltype(x) w = x; //w是一个double类型
  1. 如果前面条件都不满足,那么var类型与expression类型相同:
int j = 3;
int &k = j;
int &n = j;
decltype(j + 6) i1; //i1是int型
decltype(100L) i2; //i2是long型
decltype(k + n) i3; //i3是int型

在我们需要多次声明的情况下可以使用typedef与decltype相结合的方式:

double x = 5.5;
int y = 2;
typedef decltype(x + y) xytype;
xytype xpy = x + y;

这样就可以多次使用了。

还有一种情况:如果我们的返回值是未知类型呢?

template<class T1,class T2>
? type ? gt(T1 x, T2 x) {
	return x + y;
}

C++新增了一种函数声明方式:对于函数原型double h(int x,float y)auto h(int x,float y)->double;的函数声明方式。结合decltype则可以这样声明函数auto ft(T1 x,T2 y)->decltyoe(x+y)。(->double在这里被称为后置返回类型)
[1]: http://img.godreams.cn/images/2020/06/11/O8q4.png
[2]: http://img.godreams.cn/images/2020/06/11/OMFF.png
[3]: http://img.godreams.cn/images/2020/06/11/OBmS.png
[4]: http://img.godreams.cn/images/2020/06/15/OO9m.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Godams

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值