跳转至

C++语法

学习最终目的:了解计算机内部的工作原理和工作机制

1、基础知识、数据类型、常量与变量 
2、结构化程序设计的三种结构 用C++编写C程序(面向过程)
3、函数及变量的访问权限
4、数组与字符串
5、指针与引用
6、结构体与枚举
7、(文件)
8、(类和对象简介)

基础知识

常用运算符

&:返回变量的内存地址

sizeof:返回类型或变量的长度,单位为字节

十进制整数转二进制补码

仅考虑负数情况

  1. 求出负数绝对值的 原码;
  2. 按位取反;
  3. +1得补码。

以-106为例:

106原码: 0110 1010

按位取反得反码: 1001 0101

+1得补码: 1001 0110

二进制补码转十进制整数

参考网址:https://zhuanlan.zhihu.com/p/376848035

-1 取反 绝对值 加负号

针对负数

补码的补码就是原码

法1:

  1. 补码 - 1;
  2. 符号位不变,其余位求反;
  3. 得到的是该负数的补码

法2:

  1. 符号位不变,其余位求反;(相当于求该补码的反码)
  2. 然后再 + 1;
  3. 得到的是该负数的补码

法3:

按位加权,最左边的1按对应的-2xx加权

如-4的补码:1111 1100 -1x128+1x64+1x32+1x16+1x8+1x4=-4

整型常量

在C/C++中的四种表示方式

​ 十进制 dec ​ 二进制 0b ​ 八进制 0 oct ​ 十六进制 0x hex

cout << hex << 123 << endl; // 输出:7b
// 每次设置永久有效,直至修改

无论在源程序中表示为何种进制,和输出无关,输出缺省为十进制,可加前导进制转换符改成其他进制

无论在源程序中表示为何种进制,在内存中只有二进制补码一种

浮点型常量

书写浮点数:1.常用的标准小数点;2.E表示法

浮点数名字的由来:浮点数在内存中存储的形式,小数点可移动

浮点数在内存中存储分为三部分:符号位、指数部分和尾数部分

浮点数存储遵循 IEEE 754 规范 (学会去读专业文档)

浮点数指定有效位数(float 6位/double 15位)

1.23 double类型 1.23f float类型 2.45E20F (用E表示法表示的float型常量)

浮点数机内存储格式

IEEE 754 -> 以二进制位底数

工具网站:https://www.h-schmidt.net/FloatConverter/IEEE754.html

以1234567.7654321f为例:

按照进制转换规则,将十进制转换为二进制

这里有一个地方需要注意,不能直接在百度上搜进制转化工具,原因是计算机内部储存float型数据时只占用4个字节,而使用进制转换工具时,float型数据对应的字节要多于4个字节,也就是说使用百度上的进制转换工具相当于”提高了精度“。

#include <iostream>

using namespace std;

void erjinzhi(int num)
{
    unsigned mask;
    mask = 1u << 7;//定义一个最大位数的二进制数,首位为1,其余为0
    for (; mask; mask >>= 1)//每次1右移一位,直到mask为0
    {
        printf("%d", num & mask ? 1 : 0);//按位与运算,逐一输出num二进制数的每一位
    }

}

int main()
{
    float f = 2053932.2393502f;

    unsigned char *p = (unsigned char *) &f;
    erjinzhi((int) (*(p + 3)));
    erjinzhi((int) (*(p + 2)));
    erjinzhi((int) (*(p + 1)));
    erjinzhi((int) (*p));
    cout << endl << hex << (int) (*p) << endl;
    cout << hex << (int) (*(p + 1)) << endl;
    cout << hex << (int) (*(p + 2)) << endl;
    cout << hex << (int) (*(p + 3)) << endl;

    return 0;
}

上面的代码:拿到float类型在内存中存储的数据(也可以自己手算,分别算出整数部分和小数部分)

01001001 10010110 10110100 00111110

利用进制转换工具得到的位数远多于32位,即4个字节。

float型数据占4个字节,共32位:

31 30-23 22-0

  1. 最高位为符号位:0表示正数,1表示负数;

  2. 30-23位为指数位:如上面标红的8位数字1001 0011,对应十进制数147,根据IEEE 754规则,实际指数位为147-127,也就是20;指数位的作用很简单,其实就是控制小数点的移动

    在十进制中举例,方便理解:

    1.2345e2 = 123.45

    1.2345e3 = 1234.5

    有效位数都是5位,所以说真正决定精度的其实是尾数位

  3. 22-0位为尾数位:即二进制小数部分,0010110 10110100 00111110,在进行二进制科学记数法转换时,会在变成1.00101101011010000111110

首先回答一个疑问:这个红色的1是从哪里来的?

​ 这个还是要回到IEEE 754规则:float的表示 (1234567.7654321)10 = (1.f X 2e-127)2

​ f表示小数位,e表示指数位

​ 在二进制进制科学记数法中,始终存在红色1,因此就省略这个1,从而可以多处1位来储存小数部分

所以 1234567.7654321

= (1.00101101011010000111110 X 2147-127)2

= (1.00101101011010000111110 X 220)2

概括一下,计算机拿到一个浮点数,首先将其转换成二进制科学记数法的形式,然后分别储存指数位和小数位。

double类型同理,11位储存指数位,52位储存尾数位。

至于为什么是减127,这个暂时不深究。

转义字符

\n \t \r
// \ddd \xhh 八进制和十六进制形式
cout << '\377' << endl; // 3 * 8^2 + 7 * 8 + 7 = 255 实际输出:?
cout << '\x41' << endl; // 4 * 16 + 1 = 65 实际输出:A
// "\x61\x62\x63\061\62\063\x2a\043"
// 单引号表示字符型

换行(ASCII=10)的七种表示:

'\n' '\12' '\012' '\xA' '\x0A' '\xa' '\x0a'

'0'和"0"的区别:

'0' ASCII 48 '\60' '\060' '\x30'
"0" ASCII 0  '\00' '\000' '\x0' '\x00'

字符在内存中存储的是对应的ASCII码

字符串常量

​ 用双引号表示,由连续多个字符组成

​ 字符串的长度:字符序列中字符的个数

// 尽最大可能取
"\r\n\t\\A\\t\x1b\"\1234\xft\x2f\33" // 15个字符
// \xft -> /xf + t 两个字符
// 调用系统函数strlen(字符串), 打印字符串的长度

​ 字符串的结束标志 '\0' 也叫尾零

""" " 的区别 "" 空字符串,长度为0 \0 " " 含一个空格的字符串,长度为1 32 \0

'A'"A" 的区别 'A' 字符常量,内存中占一个字节 65 "A" 字符串常量,内存中占两个字节 65 \0

// 一般来说,汉字在内存中占两个字节
sizeof("同济") // 返回5 -> 2+2+1
sizeof("中")     // 返回3 -> 2+1

符号常量

#define PI 3.14159

变量

变量:给存储在内存中的数据起的一个名字

数据类型之间唯一的区别:当使用该数据类型创建一个变量时,将分配多少内存

float num = 5.5; // 实际上5.5类型为double

sizeof(obj) 返回变量的字节数

&a 寻址

求字符串的长度

主要考虑转义字符

\ddd 八进制

\xhh 十六进制 x必须小写

各种类型变量的使用 (重难点)

数据的溢出在C++中不认为是错误

short a=32767, b=a+1;
cout << b << endl; // -32768
// 从二进制补码角度考虑
// a 0111 1111 1111 1111
// b 1000 0000 0000 0000 对应-32768 原因:内存中存储的是变量b的二进制补码

已知二进制的补码,求对应十进制数

​ 1.最高位为0 ​ 正数 ​ 2.最高位为1 ​ 对无符号数来说,正数:正常加权即可 ​ 对有符号数来说,负数:先减1,然后符号位不变,其余位按位取反,得到该负数的原码 ​ 或者也可以通过加权来做,最高位✖️(-1),其余位正常 ​ 如-4的补码:1111 1100 -1x128+1x64+1x32+1x16+1x8+1x4=-4

同长度的signed与unsigned相互赋值时,可能出现不正确的结果

​ 内存中存储的二进制数相同,但十进制表现不同 ​ (相同位数变量,如int变量和int变量之间,赋值不影响内存中储存的数据)

short a = -10;
unsigned short b = a;
cout << b << endl; // 65526
// -10的原码 1000 0000 0000 0110 
// 1111 1111 1111 1001 符号位不取反
// -10的补码 1111 1111 1111 1010 内存中储存的数据
// 变量b对应内存储存的也是 1111 1111 1111 1010 但b为无符号数

规律:|-10|+ 65526 = 65536(一个有符号数和一个无符号数,如果它们补码一致,则其绝对值和相加等于2^xx

观看视频:https://www.bilibili.com/video/BV1iJ411W73H

以8bit为例

-1的原码:1000 0001 -1的反码:1111 1110 -1的补码:1111 1111 (和255的原码相同,why?)

8bit最多可以储存256个不同数字

若只储存正数,则储存0~255 若要同时储存负数,则储存-128~-1、0~127

天才设计者将上面两种情况进行了统一 用0~127表示正数,用128~255表示负数

负数通过其绝对值对应的补数进行表示。所谓补数,在这里指相加和为256

如上面-1的补码:-1的绝对值是1,1的补数是255,所以-1的补码和255的补码相同

打印64bit有符号数的最大值

unsigned long long x = -1LL; // 111111··· 64个1
cout << x/2 << endl; // 011111··· 63个1

​ 根据下面补充的-1的补码可知,x对应的内存储存的是64个1 ​ (64个1除2等于63个1,可以验证)

补充:-1的补码

short a = -1;
// a的原码 1000 0000 0000 0001
// a的反码 1111 1111 1111 1110
// a的补码 1111 1111 1111 1111

不同长度的整型数据相互赋值,遵循如下规则

​ 1.短 -> 长 低位赋值,高位填充(对有符号数,若为正数,全部填充0;若为负数,全部填充1;否则,全部填充0) ​ 2.长 -> 短 低位赋值,高位丢弃

cout会根据输出变量的类型智能输出

cout << '\x41' << end; // 输出A

常变量和符号常量有本质区别

​ 常变量:和变量相同,除了值不能修改 ​ 符号常量:替代

算术运算符和算术表达式

+ - 六级

* / % 五级

字符型可以参加算数运算,当作1字节的整型数,值为其ASCII码

/ 要小心,整数除以整数还是整数(舍弃小数,非四舍五入)

C++的表达式求值原则

  • 若只有一个运算符,则求值

    • eg. a + b
  • 若有两个运算符,即两个运算符作用于同一个操作数

    • 左边运算符优先级高于右边运算符
    • 或左边运算符优先级等于右边运算符,且该运算符为左结合
    • 则对左边运算符求值,其值在参与后续的运算

      • a * b + c
      • a + b + c
    • 左边运算符优先级低于右边运算符

    • 或左边运算符优先级等于右边运算符,且该运算符为右结合
    • 则先忽略左边运算符,继续向后分析,直到右边运算符被求值后再次分析左边运算符
      • a + b * c
      • a + b * c - d (先a + b * c)

字符型、整型、实型的混合运算

如果某个运算符涉及到的两个数据不是同一类型,则需要先转换成同一类型,再进行运算

​ 转换的优先级:long double; doule; float; unsigned long long; long long; unsigned long; long ; unsigned; int(char u_char short u_short)

​ 整型提升:在计算表达式时,C++将bool、char、short等转换成int

short a = 32767;
short b = a + 1; // a+1得到int,赋值给short型变量,故丢弃了前16位
cout << b << endl; // -32768

short a = 32767;
short b = 1;
cout << a + b << endl; //32768 a转成int b转成int 再进行计算

​ 同级的signed与unsigned混合时,以unsigned为准

​ 不是一次全部转成最高级,而是依次转换(求圆锥面积的例子)

自增与自减运算符

​ ++变量名:先自增,后使用 第3级 右结合 ​ 变量名++:先使用,后自增 第2级 左结合

​ 前/后缀对变量自身无影响,影响的是参与运算的表达式->所以i++和++i单独成为语句时,前后缀等价

​ 自增运算符不能对常量、表达式使用

​ 不会考察对同一个变量的多个++出现在同一个表达式中

强制类型转换

(int) (a + b);
int (a + b);
static_cast <int> (a + b);

赋值运算符和赋值表达式

变量 = 数据(常量、变量、表达式)

若赋值运算符的左右类型不同,则以左值类型为准进行转换

int a;
a = 2*3.2;
cout << a << endl; // 输出6 取整即可

赋值表达式也有值

(a = 10) * 2;
(a = 3 * 5) = 4 * 3; // 0 warning 0 error
// 虽然=左边是一个表达式,不是要求的变量
// 但先计算该表达式,赋值运算执行时左边只剩变量a

逗号运算符和逗号表达式

  • 运算级最低,第18级
  • 逗号表达式的值为最右边表达式的值
b = (a=3*5, a*4) // b=60

cout的基本理解

1、一个流插入运算符 << 只能输出1个数据

2、强制转换和隐式转换

char ch = 65;
cout << (int)ch << endl;
cout << ch + 0 << endl;

cin的基本理解

1、在前面有正确输入的情况下,回车、空格和非法字符会终止输入,在这些字符后面的字符将不被输入到缓冲区

注:终止输入不等于把值从缓冲区提取出来赋值给变量,直到按下回车才从缓冲区提取!

2、在正确输入前的空格、回车会被编译器忽略

3、直接输入非法数据,则输出0

4、cin不能读取换行符、空格

5、读取多个数据时,遇到非法字符即停止读取

6、常见错误 cin >> a, b, c; // 优先级 逗号表达式低

当cin输入错误时,会保存一个错误信息,cin.good()=0,程序全部结束后恢复正常

输入cin和赋值=的对比

对输入cin,当输入超过上限时,变量被赋值为该上限值;当输入超过下限时,变量被赋值为该下限值

而对赋值=就比较复杂,分两种情况:数值的位数是否超过变量被分配的大小

若不超过,则按照补码进行转换;若超过,则会发生截断

下面我们通过例子来说明

注意观察下面两种情况的对比

#include <iostream>
using namespace std;

int main()
{
    short k = 54321; // 超int上限,但未超unsigned int上限
    // 54321原码 1101 0100 0011 0001 整数的原码和补码相同
    // k将   1101 0100 0011 0001看作成某个数的补码
    // 减1得 1101 0100 0011 0000
    // 取反  1010 1011 1100 1111 -> -11215
    cout << k << endl; // -11215
    return 0;
}
#include <iostream>
using namespace std;

int main()
{
    short k = 70000; // 超unsigned int上限
    // 70000原码 1 0001 0001 0111 0000 整数的原码和补码相同
    // 超过16位 超出部分截断
    // k将   0001 0001 0111 0000看作成某个数的补码
    // 最高位位0 正数 原码和补码相同 -> 4464
    cout << k << endl; // 4464
    return 0;
}

赋值 负数超下限的情况暂未考虑

cin提取给char型变量

每次只从缓冲区中提取一个字符

不能提取空格、换行、回车和转义字符

eg. 当输入 \101时,变量只提取到\,其ASCII码为92,101留在缓冲区中

不要犯:cin >> a, b, c;这样的错误

对错误信息的理解:b、c未被定义 >> 优先级要高于 ,

流提取运算符>> 后必须跟变量,不能是常量和(含变量的)表达式

cin根据变量类型进行读取

空格使cin切换到下一个变量进行输入

cin后千万千万不能跟endl;

  1. cin:按格式读入,到空格、回车和非法字符为止;getchar:只读一个字符

  2. cin和getchar都有输入缓冲区,输入必须以回车结束,从输入缓冲区取得所需内容后,多余内存存放在输入缓冲区,等待下次读入(程序结束时,会清空缓冲区)

  3. _getche()_getch()没有输入缓冲区 -> 输入后不需要按回车键

  4. getchar()返回值为int型,因为除了正常的256个ASCII字符外,还需额外考虑一个输入错误情况,因此无法用1个字节返回值

putchar支持转义字符,而getchar不支持转义字符

putchar的基本使用

头文件:#include <cstdio>

作用:输出一个字符

putchar(字符常量/变量)

A \x41 \101 支持转义字符表示

返回值是int型,对应输出字符的ASCII,可赋值给字符型/整型变量

#include <iostream>
#include <cstdio>

using namespace std;

int main()
{
    char ch;
    cout << (ch = putchar('A')) << endl; // AA
    return 0;
}

解释:第一个A是由putchar输出的;第二个A是由cout输出的;

虽然putchar返回值是int,但赋值表达式的值为等号右边的值,类型为等号左边变量的类型,即char,所以cout会输出ASCII对应的字符

getchar的基本使用

头文件:#include <cstdio>

作用:输入一个字符,给指定的变量

ch = getchar()

返回值是int型,对应输入字符的ASCII,可赋值给字符型/整型变量

输入有回显,等按回车后才执行

可以输入空格、回车等cin无法处理的非图形字符,但无法处理转义字符

getchar每次只读取一个字符,cin根据变量类型读取

_getch_getche

头文件:#include

_getch:从控制台读取一个字符,无回显

_getche:从控制台读取一个字符,有回显

以上两个均无输入缓冲区,无需回车来结束输入

结构化程序设计

算法的表示

  1. 自然语言表示
  2. 流程图表示
  3. 伪代码表示
  4. 计算机语言表示 我们学的就是这个

程序的组成

  • 一个程序由若干源程序文件(.cpp)及头文件(.h)组成

  • 一个源程序文件由预处理指令(#xxx)、全局声明及若干函数组成

  • 一个函数由若干语句组成(定义语句、执行语句等)

语句:声明语句、执行语句、空语句和复合语句

流的基本概念

流的含义:流是来自设备或传给设备的一个数据流,由一系列字节组成,按顺序排列

C语言方式:printf/scanf 需要头文件 #include <stdio.h>

C++语言方式:cin/cout 需要头文件 #include <iostream>

<< 流插入运算符; >> 流提取运算符 优先级:第7级

输出流的基本操作 cout

cout << a << b << c; // 一个插入运算符只能输出一个值

插入的数据存储在缓冲区中,不是立即输出

-> 等到缓冲区满/碰到换行符('\n', 'endl')/强制立即数据 才输出

默认输出设备是显示器,可更改(输出重定向)

# 在命令行运行可执行文件
./a.out >> 1.txt // mac
main.exe >> 2.txt // win

cout会根据变量类型,自动判断输出数据的格式

输入流的基本操作 cin

cin >> a >> b >> c; // 一个提取运算符只能输入一个值
  • 输入的数据存储在缓冲区中,不是立即被提取 -> 等到缓冲区满/碰到回车符 才被提取

  • 默认输入设备是键盘,可更改(输入重定向)

  • 提取运算符后必须跟变量名,不能是常量/表达式

cin >> a + 10; // 报错,+ 第6级,>> 第7级,因此a+10先结合,变成一个表达式
  • 输入终止条件:回车、空格和非法输入

  • 系统会根据cin后变量的类型按最长原则来读取合理数据

  • cin会根据变量类型,自动判断输入数据的格式

  • 变量读取后,系统会判断输入数据是否超过变量的范围,若超过,cin.good()返回0

char ch;
cin >> ch;
// cin在输入char型变量时,只能输入图形字符(33-126),且不能通过转义字符方式输入
//(单双引号、转义符`\`均被当作单个字符)
  • cin在输入float型变量时,输出结果为十进制数或指数形式(与该数的有效数字有关),四舍五入

  • cin不能跟endl;

字符的输入和输出

putchat() 需要头文件:`#include <cstdio> 或#include `

功能:输出一个字符

返回值为int型,对应输出字符的ASCII码

getchar() 需要头文件:#include <cstdio> 或 #include <stdio.h>

功能:键盘输入一个字符,将该字符传递给指定变量

返回值为int型,对应输入字符的ASCII码

输入有回显,存在输入缓冲区,输入后需按回车结束输入

​ (如果直接按回车则得到回车的ASCII码)

可以输入空格、回车等cin无法处理的非图形字符,但仍然不能处理转义符

cin和getchar一样,每次仅从输入缓冲区中提取需要的字节,多余字节仍保留在缓冲区中

_getch()和_getche() 需要头文件:`#include ``

这两个输入函数均🈚️输入缓冲区,两者区别在于:getch无回显,而getche有回显

关系运算和逻辑运算

关系运算:将两个值进行比较,返回结果:真或假

关系运算符:

优先级:第8组 < > <= >=

​优先级:第9组 == !=

关系表达式:用关系运算符将两个表达式连接起来

​ 表达式可以是算数、逻辑、赋值和关系表达式

关系表达式的值只有1或0两种

在写条件语句时,如果要判断a既大于90,由小于100

应该写成 (a > 90 && a < 100)

错误写法:(90 < a < 100) 程序会先算 90 < a,即关系表达式,结果返回1或0,必定小于100

所以不论a为多少,都为true

关系表达式的值可以作为整型参与运算

实数(小数)参与关系运算时要考虑误差(超过有效位数的数字不可信)

逻辑常量:true/false (C++特有 C🈚️)

逻辑变量:定义 bool 变量名

在内存中占 1 个字节,表示为整型值,取值只有0/1

cin时只能输入0/1,否则结果不可信 (多种情况)

在参与赋值运算时,按 非0为真 0为假 原则

逻辑运算:多个关系表达式或逻辑常量进行运算,返回值为真/假

逻辑运算符:

&& 优先级:第13组

​|| 优先级:第14组

!  优先级:第3组 单目运算符

逻辑表达式:将多个表达式或逻辑量用逻辑运算符连接起来

什么是短路运算,如何验证?

按位与/或运算

​ 按位与 & 优先级:第10组

​ 按位或 | 优先级:第12组

数组

数组声明和访问

数组 array 能够存储多个同类型的值

要创建数组,可使用声明语句,包含三部分

  1. 存储在数组中元素的类型;
  2. 数组名;
  3. 数组中的元素

声明语句的通用格式: typeName arrayName[arraySize];

其中 arraySize 必须是整型常数或const值,也可以是常量表达式,但不能是变量!

变量的值是在程序运行时设置的,而const值是在程序编译时设置的

访问数组元素的方法:使用下标或索引来对元素进行编号

C++数组从 0 开始编号,使用带索引的方括号表示法来指定数组元素

编译器不会检查使用的是否有效!

C++允许在声明语句中初始化数组元素: int a[] = {1, 2, 3, 4};

如果没有初始化数组,则其元素值将是不确定的,原来是什么值,现在还是什么值

数组的初始化规则

C++有几条关于初始化数组的规则,它们限制了初始化的时刻,决定了数组的元素数目与初始化器中值的数组不相同时将发生的情况

  1. 只有在定义数组时才能使用初始化,此后就不能使用了;
  2. 不能将一个数组赋给另一个数组;
  3. 可以使用下标分别给数组中的元素赋值 a[4] = 3;
  4. 初始化数组时,提供的值可以少于数组的元素数目,这样的话,编译器将把其他元素设置为0
  5. 若要将数组中所有的元素都初始化为0,可以使用 int a[10] = {0};
  6. 如果方括号内为空,则C++编译器将计算元素个数

C++11 数组初始化方法

C++11 将使用大括号的初始化(列表初始化)作为一种通用初始化方式,可用于所有类型

数组以前就可使用列表初始化,但C++11 中的列表初始化新增了一些功能

  1. 初始化数组时,可省略等号 int a[4] {1, 2, 3, 4};
  2. 可不在大括号内包含任何东西,这将把所有元素都设置为0 int a[4] = {};
  3. 列表初始化禁止缩窄转换

数组指针

姑且把数组名看作一个指针

顺序查找 1.利用下标;2.利用指针

对一维数组元素的访问总共有三种方式:

  1. 通过下标 a[i]
  2. 通过地址 *(a+i)
  3. 通过指针 *(p+i) 或 p[i]

指针p和数组名a的区别:指针可以++,而数组名是一个常量指针,不能++

p++比p+i执行效率高得多

int a[10];
int *p = a;
for (int i = 0; p < (a + 10); p++)
    *p = i;

假设p和q均为一个int指针

p - q 指针p、q之间元素个数

(int)p - (int)q 字节差

一维数组作为函数的参数

通常把数组和数组长度一起传给函数,很少单独传数组

字符串

C++处理字符串的方式

字符串是存储在内存的连续字节中的一系列字符,一定是以 \0 结尾

C++处理字符串的方式有两种:

  1. C-风格字符串,来自C语言;
  2. 基于 string 类库

C-风格字符串

字符串存储在连续字节,所以可以将字符串存储在 char 数组中

C-风格字符串以空字符结尾,空字符即 \0,其ASCII码为0,用来标记字符串的结尾

要注意,字符数组并不都是字符串,上面两个数组都是 char 数组,但只有第二个数组是字符串

用上面的方法初始化字符串太麻烦!

除了使用上面这种数组初始化方法外,还有一种更好的将字符数组转换成字符串的方法

char fish[] = "Bubbles";

这种用引号括起来的字符串被称为字符串常量或字符串字面值(这种字符串代表着指向这个字符串的地址)

用括号括起来的字符串隐式地包括结尾的空字符,因此不用显式地包括它

另外,各种C++输入工具通过键盘输入,将字符串读入到 char 数组中时,也将自动加上结尾的空字符

数组长度要多留一个给空字符!

在数组中使用字符串

要将字符串存储到数组中,有两种方法:

  1. 将数组初始化为字符串常量;
  2. 将键盘或文件输入读入到数组中

标准头文件 cstring 提供了 strlen() 等很多与字符串相关的函数的声明

用 strlen() 求字符数组长度时,不计入尾0

字符串输入

cin 使用空白(空格、制表符和换行符)来确定字符串的结束位置

因此,使用 cin 读取一句话时,当遇到空格后,cin 将空格前的字符串放到数组中,并自动在结尾添加空字符,空格后的内容会被放在输入缓冲区

当我们 cin >> a; ,按回车结束输入时,换行符会被留在输入缓冲区 (已验证)

要解决这个问题,即我们需要面向行而不是面向单词进行读取

可以使用 istream 类中提供的面对行的类成员函数(或者叫方法):getline() 和 get()

(cin 是一个 istream 对象)

这两个函数都读取一行输入,直到遇到换行符

差别在于:getline() 将丢弃换行符,而 get() 将换行符保留在输入序列中,在这点上, get() 和 cin >> a 很像

简单来说, getline() 使用起来简单一些,但 get() 使得检查错误更简单些

混合输入字符串和数字

当 cin 读取年份后,将回车键生成的换行符留在输入缓冲区中,等使用 cin.getline() 时,将认为是一个空行,并将一个空字符串赋给字符数组

解决方法:在读取字符串之前,先读取并丢弃换行符,可使用 cin.get();cin.get(char);getchar();

字符指针和字符数组

字符指针 char *

char *p_str = "Hello World!";
cout << p_str << endl; // Hello World!

顺序:1.分配内存给字符指针 p_str;2.分配内存给字符串;3.将字符串的首元素地址赋值给字符指针。

也可以把 p_str 叫做字符串指针,用来存放字符串的首地址

char *p_str;
p_str = "Hello World!"; // 将字符串的首地址赋值给字符指针
cout << p_str << endl;  // Hello World!

p_str + 2 :输出 llo World!

解释:p_str指向字符串的首元素地址,所以p_str + 2指向字符串中第三个元素的地址,cout直接打印输出字符指针时,会依次打印,直到遇到尾0才停止

**ISO C++11 does not allow conversion from string literal to char

解引用操作 *

在正式提解引用操作之前,先来了解一下当字符指针指向字符串常量时,这个字符指针到底是什么

cout << p_str << endl; // Hello World!
printf("%s\n",p_str);  // Hello World!
printf("%p\n",p_str);  // 0x106babf2d

其实 p_str 本质是字符串首地址位置,至于为什么用cout打印时会输出字符串的值,我猜是重载

不管如何,这两种都要理解

char *p_str;
p_str = "Hello World!"; 
cout << p_str << endl;         // Hello World!
cout << (void *)p_str << endl; // 0x10494af2f
printf("%p\n",p_str);          // 0x10494af2f

char *p="abcd";

​ 在C语言里,输出一个字符串的指针很方便,直接 printf("%p/n",p); 就输出了。

​ 而C++里cout太自作聪明了,为了省去我们循环输出字符的麻烦,cout<<p<<endl; 被翻译为输出p指向的字符串值。

​ 这个时候要输出p的指针值就只能先将其转为void *再输出。因为void型, cout没法输出,只能乖乖输出指针。此外cout<<&p<<endl; 是不可取的,它其实等效于 printf("%p/n",&p);

​ 为什么用首地址就可以输出字符串?

因为还有一个关键,在C语言中字符串常量的本质表示其实是一个地址

以上几行摘自:https://blog.csdn.net/hairetz/article/details/4129788

char *p_str = "Hello World!";
cout << *p_str << endl;     // H
cout << *(p_str+3) << endl; // l

*p_str p_str是字符串首元素的地址,通过解引用,打印对应的字符

*(p_str+3) p_str+3是字符串中第三个元素的地址,通过解引用,打印对应的字符

字符数组 char []

字符数组是由若干个字符组成,也可以用来存放整个字符串

字符数组分两种:1.若干个字符;2.字符串 (区别:数组最后一个元素是否是 \0

如果是 \0 的话,就是字符串,否则就只是若干个字符组成的字符数组

字符数组的初始化

char s[10] = {'I', ' ', 'a', 'm', ' ', 'f', 'i', 'n', 'e'}; // 逐个字符赋初值

char s[10] = {"I am fine"}; // 字符串 系统会自动添加尾0
char s[10] = "I am fine";   // 同上

char a[4][8] = {"COBOL", "FORTRAN", "PASCAL", "C/C++"};

// char s[20];
// s = "I am fine"; 错误
  1. 可以用字符串常量来初始化字符数组,相当于整体赋值,但该方法只能在初始化时使用,不能用于字符数组的赋值;
  2. 可以对字符数组的各元素逐个赋值

string 类

https://www.bilibili.com/video/BV1sX4y1w7zx

https://blog.csdn.net/Alisa01/article/details/104275733 用数组下标输入,返回字符串是空值

string 和字符数组的关系

C++98 标准通过添加 string 类扩展了 C++ 库,因此现在可以使用 string 类型的变量(用C++的话说是对象)

而不再局限于用字符数组来存储字符串

要使用 string 类,需要头文件 #include <string>

在很多方面,使用 string 对象的方式与使用字符数组相同

  1. 可以使用 C-风格字符串来初始化 string 对象;
  2. 可以使用 cin 来将键盘输入存储到 string 对象中;
  3. 可以使用 cout 来显示 string 对象;
  4. 可以使用数组表示法来访问存储在 string 对象中的字符

string 对象和字符数组之间的主要区别:可以将 string 对象声明为简单变量,而不是数组 string str1;

类设计让程序能够自动处理 string 的大小

例如,str1 的声明将创建一个长度为 0 的 string 对象,但将程序将输入读取到 str1 中时,将自动调整 str1 的长度 cin >> str1;

赋值、拼接和附加

使用 string 类时,某些操作比使用数组时更简单

例如,不能将一个数组赋给另一个数组,但可以将一个 string 对象赋给另一个 string 对象

string 类简化了字符串合并操作,可以使用算数运算符 + 将两个 string 对象合并起来,还可以使用运算符 += 将字符串附加到 string 对象的末尾

其他操作

.size() 是 string 类成员函数 -> 传统 strlen()

string 类输入/输出

char char1[20];
cout << strlen(char1) << endl; // 很可能不是0

cin.getline(char1, 20); // 此处 getline 是 istream 类的方法 cin 是一个 istream 对象
cout << char1 << endl; 

getline(cin, str1);     // 此处 getline 是一个函数,将 cin 流对象内容输入到 str1 中
cout << str1 << endl;

对字符数组,当未初始化时,用 strlen 输出字符串长度,结果很可能不是0,即原本这块内存中有值,不为空字符

strlen长度不包括最后的空字符

关于“此处 getline 是 istream 类的方法 cin 是一个 istream 对象”的理解:

istream 是一个类,cin 是他的对象 如果你有一个类: class A; 那么你写 A a; 得到的a就是A的一个对象

Istream 类帮我们定义了 cin 对象

结构体简介

声明结构体

结构是一种比数组更灵活的数据格式,同一个结构可以存储多种类型的数据

书中对结构声明和定义的描述比较混乱,看作是一样东西

定义结构后,便可以创建这种类型的变量 inflatable hat; 书中把这个叫做声明结构变量

C++ 允许在声明结构时省略关键字 struct,但C语言不允许

在 C++ 中,结构标记的用法与基本类型名相同,这强调:结构声明定义了一种新类型

可以使用成员运算符 . 来访问各个成员 hat.volume

(访问类成员函数,如 cin.getline() 的方式正是从访问结构成员变量的方式衍生而来的)

推荐使用结构体的外部声明,即将声明放到 main() 的前面

结构体的初始化方式:

inflatable guest = 
{
        "Glorious Gloria",
        1.88;
        29.88
};
// 或者放在同一行
inflatable guest = {"Glorious Gloria", 1.88; 29.88};

和数组一样,使用逗号分隔值列表,并将这些值用大括号括起来

C++11 结构体初始化

  1. 与数组一样,C++11 也支持将列表初始化用于结构,且等号是可选的

    inflatable guest = {"Glorious Gloria", 1.88; 29.88};

  2. 如果大括号内未包含任何东西,则各个成员豆浆被设置为零;

  3. 不允许缩窄转换

可以将 string 类作为结构体的成员

#include <string>
struct inflatable
{
        std::string name;
        float volume;
        double price
};

其他结构体属性

C++ 让用户定义的类型尽可能与内置类型相同

例如,可以将结构作为参数传递给函数,也可以让函数返回一个结构

成员赋值:可以使用赋值运算符 = 将结构赋给另一个同类型的结构,这样结构中每个成员都将被设置为另一个结构中相应成员的值,即使成员是数组!要知道,在 C++ 中是不能把一个数组直接赋值给另一个数组

可以同时完成定义结构和创建结构变量的工作,只需将变量名放在结束括号的后面即可

struct perks
{
        int key_number;
    char car[12];
} mr_smith, ms_jones;

甚至还可以在创建结构变量的同时进行初始化 (不推荐这样做)

struct perks
{
        int key_number;
    char car[12];
} mr_glitz = 
{
    7, 
    "Packard"
};

还可以声明没有名称的结构类型,方法是省略名称,这样的话,就必须要创建一个结构变量

struct
{
        int x;
        int y
} position;

C 和 C++ 都具有以上的所有特性,不过 C++ 除了这些外,还可以有成员函数,但这个高级特性通常被用于类中,而不是结构体中

结构体数组

创建一个元素为结构体的数组,方法和创建基本类型数组完全相同 inflatable gifts[100];

gifts 是一个 inflatable 数组,其中的每个元素(如gifts[0])都是 inflatable 对象

可以与成员运算符一起使用 gifts[0].volume

需要强调的是,gifts 本身是一个数组,而不是结构体

枚举

枚举是用户自定义的数据类型

用枚举,本质就是为了让代码更清晰

用途:用一些整数来表示特定的状态

枚举中第一个变量默认从0开始

#include <iostream>
using namespace std;

enum STU // 定义
{
    A = 100,
    B,
    C
};

int main()
{
    STU student = B; // 声明变量并赋值
    cout << student << endl;
    return 0;
}
#include <iostream>
using namespace std;

enum STU
{
    A, // 默认从0开始
    B,
    C
}student; // 提前起名字

int main()
{
    student = B;
    cout << student << endl;
    return 0;
}
#include <iostream>
using namespace std;

enum COLOR
{
    red, // 0
    green = 10,
    blue // 11
};

int main()
{
    COLOR color;
    color = blue;
    cout << color << endl; // 11
    // color = 2;  报错!只能将枚举量赋值给枚举对象
    color = (COLOR)2; // 实在要赋值,必须要进行强制类型转换
    cout << color << endl; // 2
    return 0;
}

枚举对象不能参与算数运算

还可以指定枚举值的数据类型,注意只能用整型

enum Example : unsigned char
{
        A = 5, B, c
}

枚举主要解决一些特性属性的赋值,变量取值范围在一定范围内

如一年有十二个月,一个星期有七天

#include<stdio.h>

int main( )
{

    enum weekday {sun=1,mon,tue,wed,thu,fri,sat} day; //从1开始
    int k;
    printf("请输入今天星期几(1--7):");
    scanf("%d",&k);
    day=(enum weekday)k;
    switch(day)
    {
        case mon:
        case tue:
        case wed:
        case thu:
        case fri:       
            printf("今天上班\n"); break;

        case sun:
        case sat:       
            printf("今天休息\n"); break;

        default:        
            printf("输入有误\n"); break;
    }
    return 0;
}