1、基本数据类型和表达式
1.1基本数据类型
1.2、常量
常量:在程序运行过程中其值始终不可以改变的值,即直接使用符号表示的值。例如:125、3.14、'C'、"Hello"等都是常量。
(1)整型常量:正整数、负整数和0。表示形式有十进制、八进制和十六进制。
- 十进制形式——若干个0~9的数字,但不能以0开头。例如:12、25、127。
- 八进制形式——0 + 若干个0~7的数字。例如:073、046、063。
- 十六进制形式——0x + 若干个0~9的数字及A~F的字母(大小写均可)。例如:0x12、0X3F等。
整型常量可以使用后缀指定是都带有符号及占有多少空间,例如,后缀UL(ul)表示类型是unsigned long。
(2)实型常量
- 一般形式——例如:12.5、-12.5。
- 指数形式——例如:12.7E+2表示
,-1.5E-5表示
。其中字母E可以大写也可以小写。
实型常量默认为double型,如果后缀F(f)可以使其成为float型,例如:37.2f。
(3)字符常量:单引号(' ')括起来的一个字符,例如:'a'、'B'、'?'。
还有一些字符是不可显示字符,也无法通过键盘输入。C++提供一种称为转义字符的表示方式来表示这些字符,下表是C++预定义的转义序列:
字符常量形式 | 含义 | 字符常量形式 | 含义 |
\a | 响铃 | \f | 换页 |
\n | 换行 | \\ | 字符“\” |
\t | 水平制表符 | \" | 双引号(") |
\v | 垂直制表符 | \' | 单引号(') |
\b | 退格 | \? | 问号 |
\r | 回车 |
无论是一般字符还是不可显示字符,均可使用八进制或十六进制ASCII码来表示,并实行是如下,其中nnn表示3位八进制或十六进制。
\nnn —— 八进制形式 \xnnn —— 十六进制形式
字符数据在内存中以ASCII码的形式存储,每个字符占用1Byte,使用7个二进制位。
(4)字符串常量:也称字符串,是使用一对双引号(" ")括起的字符序列,例如:"Hello"。如果在字符串常量中使用双引号("),那么需要用转义序列来表示。例如:
"Please click \"yes\" or \"no\"." —— Please click "yes" or "no".
字符串在内存中的存放形式:按字符串中字符的排列次序顺序存放,每个字符占1Byte,并在末尾添加'\0'作为结尾标记。例如:字符'a'和字符串"a"是不同的。
(5)布尔常量:只有false(假)和true(真)。
1.3、变量
在程序的执行过程中其值可以改变的量。变量是需要用名称来标识的。
(1)变量的声明和定义
同一语句中可以声明同一类型的多个变量,声明语句的形式如下:
// 声明形式:数据类型 变量名1, 变量名2, ..., 变量名n;
// 声明一个int型变量和两个double型变量
int num;
double price, total;
需要注意的一点是,声明一个变量时只是把变量名标识符的有关信息告诉编译器,但声明并不一定引起内存的分配;而定义一个变量就是给变量分配内存空间,用于存放相应类型的数据,变量名就是对相应内存单元的命名。在C++程序中,大多数情况下声明变量就是定义变量。
(2)变量的初始化
定义一个变量的同时,为其赋予一个初始值,称为对变量的初始化。例如:
// 变量初始化
int num = 25;
double price = 3.99;
char c = 'A';
用于初始化变量的值可以是任意的表达式,在同一条定义语句中可用先定义的变量值去初始化后定义的其他变量。例如:
// 先初始化单价price,再用price去初始化总价total
double price = 3.99, total = 25 * price;
注意:在C++中,初始化和赋值是不同的操作——初始化是在创建变量时赋予其一个初始值;赋值是将变量当前值擦除,用一个新值代替。
C++提供了多种初始化方式,例如对一个int型变量a进行初始化有以下4种方式:
int a = 10;
int a(10);
// 列表初始化
int a = {10};
int a{10};
// 使用列表初始化时不允许信息的丢失
double pi = 3.1415926;
int a{ pi }, b = { pi }; // 错误:转换未执行,因为存在丢失数据的风险
int c(pi), d = pi; // 正确:转换执行,且丢失了部分数据
若定义变量时未指定初始值,则变量会被默认初始化,这个默认初始化的值是很么不仅由变量类型决定,而且定义变量的位置也会对其产生影响。定义于任何函数体外的变量被初始化为0;定义在函数体内部的基本数据类型不会被默认初始化,一个未被初始化的基本数据类型变量的值是未定义的,如果试图复制或以其他方式访问这类值将会引发错误。
1.4、符号常量
常量声明语句的形式:
const 数据类型说明符 常量名 = 常量值;
数据类型说明符 const 常量名 = 常量值;
符号常量在声明时一定要初始化,而在之后的程序中不允许改变其值。
const float PI; // 错误:常量在声明时必须被初始化
const float PI = 3.1415926;
PI = 3.14; // 错误:常量不能再被赋值
1.5、运算符与表达式
1.5.1、算术运算符与算术表达式
基本算术运算符:+(加或正号)、-(减或负号)、*(乘)、/(除)、%(取余)。它们之间的相对优先级关系与数学中是一致的,即先乘除后加减,同级运算自左向右进行。
C++中的++(自增)、--(自减)运算符是使用方便的两个运算符,它们都是一元运算符。它们的执行效果:
int i = 12;
// 后置自增i++、后置自减i-- —— 先使用后加减
// 前置自增++i、前置自减--i —— 先加减后使用
cout << i++; // 首先使i自增为13,但是输出的是i自增前的值12
cout << ++i; // 在运算前i的值为13,执行时先使i自增为14,最后输出的值为14
cout << i--; // i自减为13,输出的仍是自减前的值14
cout << --i; // i先自减为12, 输出的值为自减后的值12
1.5.2、赋值运算符与赋值表达式
基本赋值运算符:“=”。
复合赋值运算符:
复合赋值操作 | 等价操作 | 复合赋值操作 | 等价操作 |
a += 10; | a = a + 10; | a <<= 2; | a = a << 2; |
a -= 5; | a = a - 5; | a >>= 3; | a = a >> 3; |
a *= 2; | a = a * 2; | a &= 7; | a = a & 7; |
a /= 2; | a = a / 2; | a ^= 5; | a = a ^ 5; |
a % 3; | a = a % 3; | a |= 6; | a = a | 6; |
1.5.3、逗号运算符和逗号表达式
// 逗号运算符使用形式:
表达式1, 表达式2
// 先求解表达式1, 再求解表达式2, 最终结果为表达式2的值
int a;
a = 3 * 5, a *= 4; // 最终结果为60
1.5.4、逻辑运算与逻辑表达式
关系运算符是比较简单的一种逻辑运算,关系运算符及其优先次序为:
用关系运算符将两个表达式连接起来,就是关系表达式。关系表达式是最简单的逻辑表达式,其结果类型为bool,值只能为true或false。
int a = 5, b = 3, c = 9;
a > b // true
a + b >= c // false
(a + b) != c // true
C++中的逻辑运算符及其优先次序为:
逻辑运算符的真值表:
1.5.5、条件运算符与条件表达式
C++中唯一一个三元运算符是条件运算符“?”, 它能够实现简单的选择功能。条件表达式的形式为:
// 表达式1必须是bool类型,表达式2,3可以是任何类型
表达式1 ? 表达式2 : 表达式3
简单示例:求出两个整型变量a和b中的最小值。
cout << (a < b) ? a : b;
1.5.6、sizeof运算符
sizeof运算符用于计算某种类型的对象再内存中所占的字节数。其使用的语法形式为:
sizeof(类型名)
sizeof(表达式)
示例:
int a = 5, b = 3;
cout << sizeof(int) << endl; // 输出4
cout << sizeof(a + b) << endl; // 输出4
1.5.7、位运算
(1)按位与(&)——清0操作
按位与操作是将两个操作数的二进制形式对应的每一位分别进行逻辑与操作。
例如:计算3 & 5
(2)按位或(|)——置1操作
按位或操作是将两个操作数的二进制形式对应的每一位分别进行逻辑或操作。
例如:计算3 | 5
(3)按位异或(^)——翻转若干指定位
按位异或操作是将两个操作数的二进制形式对应的每一位分别进行异或操作。具体运算规则是:若对应位相同,则该位的运算结果为0;若对应位不同,则该位的运算结果为1。
例如:计算0x39 ^ 0x2a
(4)移位(<< 或 >>)
左移(<<):按照指定位数将一个数的二进制值向左移位。左移后,低位补0,移出的高位舍弃。
右移(>>):按照指定位数将一个数的二进制值向左移位。右移后,移出的低位舍弃。如果是无符号数则高位补0;如果是有符号数,则高位补符号或补0。
1.5.8、运算符优先级与结合性
1.5.9、混合运算时数据类型的转换
(1)隐式转换
在算术运算和关系运算中如果参与运算的操作数类型不一致,编译系统会自动对数据进行转换(即隐式转换)。转换的基本原则:将低类型数据转换为高类型数据。各种类型的高低顺序为:
(2)显式转换
将表达式的结果类型转换为另一种指定的类型。显式类型转换语法形式:
类型说明符(表达式) // C++风格的显式转换符号
(类型说明符)表达式 // C语言风格的显式转换符号
例如,将float类型的PI转换为int类型的pi。
#include <iostream>
using namespace std;
int main() {
float PI = 3.14, circle;
int pi;
pi = (int)PI; // 将float型转换为int型时,取整数部分,舍弃小数部分
circle = 5 * 5 * PI; // 转换是暂时的,现在的PI仍是3.14
cout << "pi = " << pi << endl; // 输出3
cout << "circle = " << circle << endl; // 输出78.5
return 0;
}
使用显示类型转换的注意事项:
- 转换不安全。将高类型数据转换为低类型数据时,会丢失数据精度。
- 转换是暂时的、一次性的。
2、数据的输入与输出
2.1、预定义的插入符和提取符
“<<”是预定义的插入符,作用在流类对象cout上便可以实现一般的屏幕输出。
// 输出hello world!
cout << "hello world!" << endl;
cout << "hello " << "world!" << endl;
“>>”是预定义的提取符,作用在流类对象cin上便可实现一般的键盘输入。
// 输入a,b的值,中间需以空格分隔
int a, b;
cin >> a >> b;
2.2、简单的I/O格式控制
若要使用如下的操纵符,首先必须在源程序中添加iomanip头文件。
简单示例:
#include <iostream>
#include <iomanip>
#include <bitset>
using namespace std;
int main() {
int a = 57;
float b = 3.1415926;
cout << "十进制:"<< dec << a << endl; // 输出57
cout << "十六进制:" << hex << a << endl; // 输出39
cout << "八进制:" << oct << a << endl; // 输出71
// 10是二进制位数,自定义为多少输出就是多少位,高位补0
cout << "二进制:" << bitset<10>(a) << endl; // 输出0000111001
// 使用 setw 可以设置输出的宽度。如果输出内容的字符数少于设置的宽度,剩余部分将用空格填充。
cout << setw(5) << b << endl; // 输出3.14159
// 使用 setprecision 可以设置设置浮点数的有效位数。
cout << setprecision(3) << b << endl; // 输出3.14
return 0;
}
3、算法的基本控制结构
3.1、用if语句实现选择结构
语法形式:
if (表达式) {
语句1
} else {
语句2
}
执行顺序:首先计算表达式的值,若表达式为true则执行语句1,否则执行语句2.其流程图如下所示:
3.2、多重选择结构
3.2.1、嵌套的if语句
if (表达式1)
if (表达式2) 语句1
else 语句2
else
if (表达式3) 语句3
else 语句4
3.2.2、if···else if语句
如果if语句的嵌套都是发生在else分支中,就可以应用if···else if语句。语法形式:
if (表达式1) 语句1
else if (表达式2) 语句2
else if (表达式3) 语句3
···
else 语句n
其执行流程图如下:
3.2.3、switch语句
需要进行多次判断选择,但是每次都是判断同一表达式的值,这时就没有必要在每个嵌套的if语句中都计算一遍表达式的值,可以使用switch语句来解决此问题。switch语句的语法形式:
switch (表达式)
{
case 常量表达式1: 语句1
case 常量表达式2: 语句2
···
case 常量表达式n: 语句n
default: 语句n+1
}
执行顺序:首先计算switch语句中表达式的值,然后在case语句中寻找值相等的常量表达式,并以此为入口标号,由此开始顺序执行。如果没有找到相等的常量表达式,则从“default: ”开始执行。
使用switch语句时的注意事项:
- switch语句后面的表达式可以是整型、字符型、枚举型。
- 每个常量表达式的值不能相同,但次序不影响执行结果。
- 每个case分支可以有多条语句,但不必使用{}。
- 每个case语句只有一个入口标号,并不能确定执行的终止点,因此每个case分支最后应该加break语句,用来结束整个switch结构,否则会从入口点开始一致执行到switch结构的结束点。
- 当若干分支需要执行相同操作时,可以使多个case分支共用一组语句。
#include <iostream>
using namespace std;
int main()
{
// 局部变量声明
char grade = 'D';
switch (grade)
{
case 'A':
cout << "非常优秀!" << endl;
break;
case 'B':
cout << "很棒!" << endl;
break;
case 'C':
cout << "做得好" << endl;
break;
case 'D':
cout << "通过,继续努力!" << endl;
break;
case 'F':
cout << "最好再试一下" << endl;
break;
default:
cout << "无效的成绩" << endl;
}
cout << "您的成绩是 " << grade << endl;
return 0;
}
3.3、循环结构
3.3.1、while语句
while (表达式)
语句
执行顺序:先判断表达式中的循环控制条件的值,若表达式的值为true,再执行循环体。如下是while循环的流程图:
注意:一般在循环体中需要包含改变循环条件表达式值的语句,否则便会造成无限循环(死循环)。如while(1)循环就是一个死循环;
// 求自然数1~100的和
#include <iostream>
using namespace std;
int main() {
int i = 1, sum = 0;
while (i <= 100) {
sum += i;
i++;
}
cout << "1~100的和为:" << sum << endl; // 输出5050
return 0;
}
3.3.2、do···while语句
do 语句
while(表达式);
执行顺序:先执行循环体语句,后判断循环条件表达式的值,表达式为true时,继续执行循环体,表达式为false则结束循环。如下是do···while语句的流程图:
// 求自然数1~100的和
#include <iostream>
using namespace std;
int main() {
int i = 1, sum = 0;
do {
sum += i;
i++;
} while (i <= 100);
cout << "1~100的和为:" << sum << endl; // 输出5050
return 0;
}
while和do···while的区别:在控制循环条件为假时,do···while至少执行一次循环体,while一次也不执行。
3.3.3、for语句
for (初始语句; 表达式1; 表达式2)
语句
执行流程:首先执行初始语句,再计算表达式1(循环控制条件)的值,并根据表达式1的值判断是否执行循环体。若表达式1的值为true,则执行一次循环体;如果表达式1的值为false,则退出循环。每执行一次循环体后,计算表达式2的值,然后再计算表达式1,并根据表达式1的值决定是否继续执行循环体。如下是for循环的流程图:
使用for循环注意事项:
初始语句、表达式1和表达式2都可省略,分号不能省略。若初始语句、表达式1和表达式2都省略,如for(;;),就会无终止地执行循环体(死循环)。
// 求自然数1~100的和
#include <iostream>
using namespace std;
int main() {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
cout << "1~100的和为:" << sum << endl; // 输出5050
return 0;
}
3.4、其他控制语句
(1)break语句
出现在switch语句或循环体中,使程序从循环体和switch语句内跳出,继续执行逻辑上的下一条语句。break语句不宜用在别处。
(2)continue语句
可以出现在循环体中,其作用是结束本次循环,接着开始判断决定是否继续执行下一次循环。
(3)goto语句
goto 语句标号
其中,“语句标号”是用来标识语句的标识符,放在语句的最前面,并用冒号(:)与语句分开。goto语句是使程序的执行流程跳转到语句标号所指定的语句。使用goto语句会破环程序的结构,应该少用或者不用。
4、类型别名与类型判断
4.1、类型别名
传统方法是使用关键字typedef,将一个标识符声明成某个数据类型的别名,然后将这个标识符当作数据类型使用。其声明语法:
typedef 已有类型名 新类型名表;
其中,新类型名表中可以有多个标识符,它们之间以逗号分隔。也就是说,在一个typedef语句中可以为一个已有数据类型声明多个别名:
typedef string Name; // 为string创建一个别名Name
typedef int Age, Id; // 为int创建两个别名,分别为Age和Id
Name name1, name2; // name1和name2是string类型
Age age1, age2; // age1和age2是int类型
Id id1, id2; // id1和id2是int类型
另一种方法是使用别名声明来定义一个类型别名。其语法形式为:
using 新类型名 = 已有类型名;
与typedef不同的是,别名声明只能为已有数据类型声明一个别名:
typedef int Age, id;
// 等价形式
using Age = int;
using Id = int;
4.2、auto类型与decltype类型
auto类型说明符可以让编译器通过初始值自动推断变量的类型。显然定义auto变量必须要有初始值。例如:
auto val = val1 + val2;
val的类型取决于表达式val1 + val2的类型,若val1和val2都是int类型,则val也是int类型。
auto类型也可以定义多个变量,但是一个声明中只能有一种变量类型,因此变量的初始表达式类型需要一致。例如:
auto i = 0, j = 1; // 正确:i,j都是int类型
auto radius = 2, pi = 3.14; // 错误:radius和pi类型不一样
在某些情况下,定义一个变量与某一表达式的类型相同,但并不想用该表达式初始化这个变量,这时需要decltype变量,其作用时返回操作数的数据类型。使用decltype时需要紧跟一个圆括号(),圆括号内为一个表达式,声明的变量与该表达式类型一致。其声明语法为:
// j以2作为初始值,类型与i一致
decltype(i) j = 2;