C++中endl的本质是什么

1. endl的本质

自从在C语言的教科书中利用Hello world程序作为学习的起点之后,很多程序设计语言的教科书都沿用了这个做法。我们写过的第一个C++程序可能就是这样的。

#include <iostream>
using namespace std;

int main(){
 cout<<"Hello world"<<endl;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

学习过C语言的程序猿自然会把输出语句与C语言中的输出语句联系起来,也就是说: 
cout<<”Hello world”<<endl;相当于printf(“Hello world\n”);由于endl会导致输出的文字换行,自然而然地我们会想到endl可能就是换行符’\n’。

但是,如果我们定义char c=endl;会得到一个编译错误,这说明endl并不是一个字符,所以应该到系统头文件中去查找endl的定义。通过VS2012转到定义,找到了endl的定义如下:

template<class _Elem,class _Traits> inline basic_ostream<_Elem, _Traits>&
__CLRCALL_OR_CDECL endl(basic_ostream<_Elem, _Traits>& _Ostr)
{   // insert newline and flush stream
    _Ostr.put(_Ostr.widen('\n'));
    _Ostr.flush();
    return (_Ostr);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

从定义中看出,endl是一个函数模板,它实例化之后变成一个模板函数,其作用如这个函数模板的注释所示,插入换行符并刷新输出流。其中刷新输出流指的是将缓冲区的数据全部传递到输出设备并将输出缓冲区清空。

2.cout<< endl的介绍

endl是一个函数模板,再被使用时会实例化为模板函数。但是函数调用应该使用一对圆括号,也就是写成endl()的形式,而在语句cout<<”Hello world”<<endl;中并没有这样,原因何在?

在头文件iostream中,有这样一条申明语句:extern ostream& cout;这说明cout是一个ostream类对象。而<<原本是用于移位运算的操作符,在这里用于输出,说明它是一个经过重载的操作符函数。如果把endl当做一个模板函数,那么cout<<endl可以解释成cout.operator<<(endl);由于一个函数名代表一个函数的入口地址,所以在cout的所属类ostream中应该有一个operator<<()函数的重载形式接受一个函数指针做参数。

查找ostream类的定义,发现其实是另一个类模板实例化之后生成的模板类,即:

typedef basic_ostream<char, char_traits<char> > ostream;
 
 
  • 1

所以,实际上应该在类模板basic_ostream中查找operator<<()的重载版本。在头文件ostream中查找basic_ostream的定义,发现其中operator<<作为成员函数被重载了17次,其中的一种:

typedef basic_ostream<_Elem, _Traits> _Myt;

_Myt& __CLR_OR_THIS_CALL operator<<(_Myt& (__cdecl *_Pfn)(_Myt&))
{   // call basic_ostream manipulator
    _DEBUG_POINTER(_Pfn);
    return ((*_Pfn)(*this));
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在ostream类中,operator<<作为成员函数重载方式如下:

ostream& ostream::operator<<(ostream& (*op)(ostream&))  
{  
    return (*op)(*this);  
}  
 
 
  • 1
  • 2
  • 3
  • 4

这个重载正好与endl函数的申明相匹配,所以<<后面是可以跟着endl 。也就是说,cout对象的<<操作符接收到endl函数的地址后会在重载的操作符函数内部调用endl函数,而endl函数会结束当前行并刷新输出缓冲区。

为了证明endl是一个 函数模板,或者说endl是一个经过隐式实例化之后的模板函数,我们把程序改造如下:

#include <iostream>
using namespace std;

int main(){
cout<<"Hello world"<<&endl;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这个程序可以正常运行,并且结果完全同上一个程序。原因是对于一个函数而言,函数名本身就代表函数的入口地址,而函数名前加&也代表函数的入口地址。

3.endl其实是IO操纵符

实际上,endl被称为IO操纵符,也有翻译成IO算子的。IO操作符的本质是自由函数,他们并不封装在某个类的内部,使用时不采用显示的函数调用的形式。在< iostream>头文件中定义的操纵符有:

    endl:输出时插入换行符并刷新流
    endls:输出时在字符 插入NULL作为尾符
    flush:刷新缓冲区,把流从缓冲区输出到目标设备,并清空缓冲区
    ws:输入时略去空白字符
    dec:令IO数据按十进制格式
    hex:令IO数据按十六进制格式
    oct:令IO数据按八进制格式
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在< iomanip>头文件中定义的操作符有:

    setbase(int)
    resetiosflags(long)
    setiosflags(long)
    setfill(char)
    setprecision(int)
    setw(int)
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这些格式控制符大致可以替代ios的格式函数成员的功能,且使用比较方便。例如,为了把整数345按16进制输出,可以采用两种方式:

    int i=345;
    cout.setf(ios::hex,ios::basefield);
    cout<<i<<endl;
 
 
  • 1
  • 2
  • 3

或者:

cout<<hex<<i<<endl;
 
 
  • 1

可以看出采用格式操纵符比较方便,二者的区别主要在于:格式成员函数是标准输出对象cout的成员函数,因此在使用时必须和cout同时出现,而操纵符是自由函数,可以独立出现,使用格式成员函数要显示采用函数调用的形式,不能用IO运算符”<<”和”>>”形成链式操作。

4.自定义格式操纵符

除了利用系统预定义的操纵符来进行IO格式的控制外,用户还可以自定义操纵符来合并程序中频繁使用的IO读写操作。定义形式如下:

输出流自定义操纵符:

ostream &操纵符名(ostream &s)
{
  自定义代码
  return s;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

输入流自定义操纵符:

istream &操纵符名(istream &s{
  自定义代码
  return s;
}
 
 
  • 1
  • 2
  • 3
  • 4

示例代码如下:

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

 std::ostream& OutputNo(std::ostream& s)//编号格式如:0000001
 {
     s<<std::setw(7)<<std::setfill('0')<<std::setiosflags(std::ios::right);
 return s;
 }

 std::istream& InputHex (std::istream& s)//要求输入的数为十六进制数
 {
     s>>std::hex;
    return s;
 }

int main()
{
    std::cout<<OutputNo<<8<<std::endl;
    int a;
    std::cout<<"请输入十六进制的数:";
    std::cin>> InputHex >>a;
    std::cout<<"转化为十进制数:"<<a<<std::endl;
    return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

程序运行结果: 
这里写图片描述

程序中OutputNo和InputHex都是用户自定义的格式操纵符,操作符的函数原型必须满足cout对象的成员函数operator<<()的重载形式:

ostream& ostream::operator<<(ostream& (*op)(ostream&));
 
 
  • 1

所以只要编写一个返回值为std::ostream&,接收一个类型为std::ostream&参数的函数,就可以把函数的入口地址传递给cout.operator<<(),完成格式操纵符的功能。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008[P326-P329] 
[2]http://blog.csdn.net/k346k346/article/details/46836029

<think>嗯,用户之前问了关于C++中整型变量初始化的问题,现在又问到了char类型。这说明他们可能在逐步学习C++的基础数据类型。需要确保回答清晰,涵盖用户可能需要的所有基本信息,同时不重复之前的内容。 用户的问题是关于C++中的char,首先需要定义char是什么。要说明char是字符类型,占用1字节,用于存储ASCII字符,取值范围是-128到127或0到255,这取决于是否有符号。 接下来,应该提到char的常见用途,比如处理字符、字符串,或者作为小整数使用。可能还需要区分signed char和unsigned char,以及编译器默认的符号性可能不同,这点很重要,因为不同平台可能有不同行为。 用户可能还想知道char和其他字符类型(如wchar_t、char16_t、char32_t)的区别,特别是在处理Unicode时的应用。需要简要说明这些类型的作用和引入的C++标准版本。 另外,用户可能对char的内存布局和与整数的转换感兴趣,比如如何将char转换为int,或者反过来。示例代码会有帮助,比如如何输出字符的ASCII值,或者进行大小写转换。 还要注意潜在的问题,比如溢出风险,当用char存储超出范围的值时会发生什么。比如,如果赋值为128而char是有符号的,会导致负数,这可能引发bug。 用户可能没有明确提问,但可能想知道何时使用char而不是其他整数类型。需要指出在需要处理字符或节省内存时使用char,而在需要更大数值范围时选择其他类型。 最后,总结char的关键点,并给出实际例子,比如字符串操作、ASCII转换等,帮助用户更好地理解应用场景。确保语言简洁,结构清晰,符合之前设定的格式要求,比如使用列表和分段,避免复杂术语,保持口语化。</think>以下是关于C++中`char`类型的逐步解析: --- ### 1. **基本定义** - **`char`** 是C++中的**基础数据类型**,全称为“character”(字符)。 - **存储内容**:专门用于存储单个字符(如字母、数字、符号)或小整数。 - **内存占用**:通常占用 **1字节(8位)**,具体由编译器实现定义(可通过`sizeof(char)`验证,结果恒为1)。 --- ### 2. **取值范围** - **有符号`char`**:范围是 **$-128$ 到 $127$**(二进制补码表示)。 - **无符号`char`**:范围是 **$0$ 到 $255$**。 - **默认符号性**:C++标准未规定`char`默认是否有符号,由编译器决定。例如: - GCC中可通过编译选项`-fsigned-char`或`-funsigned-char`显式指定。 - 若需明确符号性,应使用`signed char`或`unsigned char`。 --- ### 3. **常见用途** #### (1) **存储字符** ```cpp char letter = 'A'; // 存储字符'A'(ASCII值为65) char symbol = '#'; // 存储字符'#' ``` - 字符需用**单引号**`' '`包裹。 - 实际存储的是字符的ASCII码值(例如`'A'`对应整数65)。 #### (2) **处理字符串** - `char`数组可表示字符串(以空字符`'\0'`结尾): ```cpp char str[] = "Hello"; // 等效于 {'H', 'e', 'l', 'l', 'o', '\0'} ``` #### (3) **作为小整数** ```cpp char small_num = 100; // 存储整数100 unsigned char byte = 0xFF; // 存储255(无符号) ``` --- ### 4. **特殊字符与转义序列** - **转义字符**:用于表示不可打印字符,如: ```cpp char newline = '\n'; // 换行符(ASCII 10) char tab = '\t'; // 制表符(ASCII 9) char null_char = '\0';// 空字符(ASCII 0) ``` --- ### 5. **与其他字符类型的对比** | 类型 | 用途 | 字节数 | 标准引入 | |-------------|-------------------------------|--------|----------| | `char` | ASCII字符或小整数 | 1 | C++98 | | `wchar_t` | 宽字符(如Unicode) | 2或4 | C++98 | | `char16_t` | UTF-16编码字符 | 2 | C++11 | | `char32_t` | UTF-32编码字符 | 4 | C++11 | --- ### 6. **注意事项** - **溢出风险**:若赋值超出范围,可能引发未定义行为: ```cpp char c = 128; // 若char为有符号类型,实际值为-128(溢出)! ``` - **与整型的隐式转换**: ```cpp int num = 'A'; // num = 65(自动转换) char c = 65; // c = 'A' ``` --- ### 示例代码 ```cpp #include <iostream> using namespace std; int main() { char ch = 'Z'; cout << ch << endl; // 输出:Z cout << (int)ch << endl; // 输出:90(ASCII值) ch = ch + 32; // 转换为小写字母'z'(ASCII 122) cout << ch << endl; // 输出:z return 0; } ``` --- ### 总结 - **`char`的本质**:是**整数类型**的细分,专门用于存储字符或小范围整数。 - **灵活但需谨慎**:需注意符号性、溢出问题和字符编码规则。 - **扩展应用**:在底层编程(如内存操作、文件I/O)中常用于处理字节数据。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值