C++笔记(一)

  1. 文件输入输出
  2. 函数模板、重载以及函数解析
  3. 小结

1、ostream的一些小知识

(1)

由继承的特性知道:基类引用可以指向派生类函数,而无需进行强制转换。
则例如,参数类型为ostream &的函数可以接受ostream对象(如cout)
或声明的ofstream 对象作为参数。

(2)open()

1
2
void open(const char *filename, ios::openmode mode);
//还有一个prot属性,很少用到,默认为0,即普通文件

其中打开文件方式(mode值)有:

方式 含义
ios::in 为输入(读)而打开文件
ios::out 为输出(写)而打开文件
ios::ate 初始位置设为文件尾
ios::app 所有输出附加在文件末尾
ios::trunc 如果文件已存在则先删除文件
ios::binary 二进制方式

这些方法可以组合使用,以或运算(’|’)的方式连接。
可用方法is_open()检测该文件是否存在

(3)setf()

setf()是ostream和ofstream对象的方法,用于设置格式标志来控制输出形式:

1
2
3
fmtflags setf(fmtflags) //第一原型
fmtflags setf(fmtflags, fmtflags) //第二原型
//fmtflags 是 bitmask 类型(一种用来存储各个位值的类型)的 typedef 名

fmtflags即为格式控制符,有:

控制符 功能
oct 用八进制格式显示数值
dec 用十进制格式显示数值
hex 用十六进制格式显示数值
left 输出调整为左对齐
right 输出调整为右对齐
scientific 用科学记数法显示浮点数
fixed 用正常的记数方式显示浮点数
showbase 输出时显示所有数值的基数
showpoint 显示小数点和额外的0
showpos 在非负数值前显示“+”
skipws 当一个流进行读取时,跳过空白字符(spaces,tabs,newlines)
unitbuf 在每次插入以后,清空缓存区

还有一些未列出,可参考C++ primer plus第17章

然后如果需要将调用的setf()效果消除,可以使用unsetf()函数,简单来说,在位模式里(mask),setf()将位设置为1,而unsetf()函数将位恢复为0。(unsetf()函数中的控制符即上述加前缀no即可)
然后对于setf()函数的第二原型,第一个参数指出要设置哪些位,第二个参数指出要清除哪些位(即相当于unsetf()),下面是一些示例:

1
2
3
4
5
6
//由于这些控制符都是ios_base类的,所以需要加上作用域解析运算符
cout.setf(ios_base::left)//对所有cout的输出进行左对齐调整
cout.setf(ios_base::showpos)//输出“+”
cout.unsetf(ios_base::noshowpos)//取消输出“+”
cout.setf(ios_base::oct,ios_base::basefield)//先清除其他位(例如dec),再将oct对应位设为1,表示使用八进制
cout << scientific << 255.555 << endl;//可使用重载的<<运算符来直接使用控制符

注:定点表示法指是用格式123.4来表示浮点值,而不管数字长度如何;科学记数法则指使用格式1.23e04,而不考虑数字长度。

iomanip头文件

通过iostream工具来设置一些格式值(如字段宽度)不太方便,为简化工作,C++在头文件iomanip中提供了其他控制符。其中三个最常用的是:

  • setprecision(n): 设置实数的精度为n位。在以一般十进制小数形式输出时,n代表有效数字.
  • setfill©: 设置填充字符c,c可以是字符常量或字符变量
  • setw(n): 设置字段宽度为n位.
  • setbase(n): 设置整数的基数为n(n只能是16,10,8)
    示例:
1
2
cout << setfill('*') << setw(8) << 57 <<endl;
//输出:******57 (事实上endl也是一种控制符)

2、函数模板

简介

C++新增特性——函数模板,是通用的函数描述。允许以泛型(而不是具体类型)的方式编写程序,因此有时也被称为通用编程。由于类型是用参数表示的,因此模板特性有时也被称为参数化类型。
例如:

1
2
3
4
5
6
7
8
9
template <typename AnyType>
//在C++98添加关键字typename前,使用关键字class创建模板
void Swap(AnyType &a, AnyType &b)
{
AnyType temp;
temp = a;
a = b;
b = temp;
}

上述函数可对多种数据类型的变量进行交换,需要注意的是,函数模板不能缩短可执行程序,也就是说用于多种类型时,实际上会生成多个函数,它的好处仅在于使得生成多个函数定义更简单可靠。
但是可以看出一个纰漏,编写的模板函数很可能无法处理某些类型。另外,有时候通用化是有意义的,就比如定义一个将两个数相加的模板函数,但是作用于两个结构体是就不行了,但是可能结构体中的成员值相加是有意义的,那么这种情况下,第一种解决方案是允许重载运算符+;第二种解决方案是为特定类型提供具体化的模板定义。下面介绍第二种方法。

显式具体化

可以提供一个具体化函数定义——称为显式具体化(explicit specialization),其中包括所需的代码。当编译器找到与函数调用匹配的具体化定义时,将使用该定义,而不再寻找模板。
C++98标准如下:

  • 对于给定的函数名,可以有非模板函数、模板函数和显式具体化函数以及它们的重载版本
  • 显式具体化的原型和定义应以template<>打头,并通过名称来指出类型
  • 具体化优先于常规模板,而非模板函数优先于具体化和常规模板

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct student//结构定义
{
char name[40];
int age;
} stu;

template <typename T>//模板函数
void swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}

template <> void swap<stu>(stu &s1, stu &s2)//表明是stu的一个具体化
{
int t;
t = s1.age;
s1.age = s2.age;
s2.age = t;
}

有关显式实例化可见C++ primer plus第8章8.5.4节

重载解析

对于函数重载、函数模板和函数模板重载,C++需要采取一个定义良好的策略,来决定为函数调用使用哪一个函数定义,尤其是有多个参数时。这个过程被称之为重载解析(overloading resolution)
在此不介绍过程是如何进行的(大概就是先找参数数目正确的函数,然后利用一套(也许是)半定量的指标来排序,然后选择最佳匹配)
具体参考顺序如下:

  1. 完全匹配,但常规函数优先于模板
  2. 提升转换 (例如,char和shorts自动转换为int,float自动转换为double)
  3. 标准转换 (例如,int转换为char,long转换为double)
  4. 用户自定义的转换,如类声明中定义的转换

如果有多个匹配的原型,编译器无法完成重载解析过程;如果没有最佳的可行函数,则编译器会打印生成一条错误信息,该消息可能会使用诸如"ambiguous"(二义性)这样的词语。

小结

  • 首先,之前老是有一种错误的观念,即我能搜索或是翻书找到的东西,我便默认为已有的东西,没有再次整理发表的任何意义。但是事实上,我自己在搜索时也会注意文章的发布时间、最近更新等,如果都和我一个观念,那岂不是固步自封了?即使我任何自己的理解也没有附加,但并不妨碍我将其作为2022.3.28的一份有关c++笔记的文章po出来。所以说,不止创造市场上没有的新品能博人眼球,模仿炮制畅销品也能分一杯羹,不能固化思维。
  • 其次,之前一直没有想过把书上的东西记录下来,虽然现在这样花了一些时间,不过依然觉得是值得的,这种学习记录式的形式更能加深印象,在某种意义上就是一个项目的创立和实践,现在的总结或是以后的回顾都是复盘。建立博客最本质的目的并非写给别人,而是记录自己。
  • C++ primer plus真是大块头,现在一半都没看到,然而2022已经过了四分之一了,道阻且长啊,想想还有好多坑没有填,又要电工阶段测验了,而且我花在数学物理上的时间可谓屈指可数(笑死,可能还没有英语多)
    已经有寄的感觉了……

C++笔记(一)
https://zongjy.github.io/2022/03/28/d0fa9c2305f3/
作者
zongjy
发布于
2022年3月28日
许可协议