Skip to content

c-c基础入门(完结)

前言


  • 本篇旨在帮助初学者快速上手 C/C++ 语言基本语法和基础操作。
    • 由于篇幅有限及为激发读者动手能力,本篇部分内容除案例外暂不详细展开。例如:数组及函数,除案例所示 int 类型外,还有 doublechar 等其他类型,需读者自行上机尝试。
    • 本篇关于 C++ 语言中面向对象编程的内容单独成篇,具体参阅C++面向对象程序设计
    • 本篇大量参考cppreference.comOI Wiki
    • 鉴于本人水平有限,文中不足与错误之处以 C/C++ 官方文档为准,如有建议,欢迎指出。
    • 推荐初学者参考本篇学习之余进行练习:
  • 练习平台:计算机学院线上评测OJ
  • 参考题解:HAUE计算机学院OJ题解

1. C/C++初识


1.1 第一个C/C++程序


C程序

cpp
#include <stdio.h>

int main() {
    printf("Hello, world!");
    return 0;
}

C++程序

cpp
#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world!";
    return 0;
}

关于 #include

  • #include 是一个预处理命令,用于将指定文件的内容插入到该指令处,被插入的文件称为头文件。
    • 头文件中包含一些初始定义,例如在 C 语言的头文件 &lt;stdio.h&gt; 中包含了对 printf() 函数的声明,因此添加 #include &lt;stdio.h&gt; 后便可直接使用 printf() 函数。
    • 除了 C 语言常用的 #include &lt;stdio.h&gt;C++ 常用的 #include &lt;iostream&gt; 外,还有许多其他头文件,它们包含不同的函数原型或模板,使用相应函数时需包含对应的头文件。
    • #include 也可用于包含用户自定义的头文件,此时应使用双引号("")而非尖括号(<>)。

关于 using namespace std;

stdC++ 标准库所使用的命名空间。

  • C++ 的命名空间机制可以用来解决复杂项目中名字冲突的问题。
  • 例如:C++ 标准库的所有内容均定义在 std 命名空间中,如果你定义了一个叫 cout 的变量,则可以通过 cout 来访问你定义的 cout 变量,通过 std::cout 访问标准库的 cout 对象,而不用担心产生冲突。
  • using namespace std; 会将 std 中的所有名字引入,我们就可以用 cout 代替 std::cout
cpp
#include <iostream>
int main(){
    std::cout << "Hello,world!" ;
    return 0;
}

关于 main()

  • main() 称为主函数,程序运行时就会先执行 main() 中的代码。
    • 实际上,main 函数是由系统或外部程序调用的。如,你在命令行中调用了你的程序,也就是调用了你程序中的 main 函数。

关于 return 0;

  • return 0; 表示程序运行 main 函数后的返回值,在一般情况下,程序结束时返回 \(0\) 表示一切正常,否则返回值表示错误代码。
    • 在 C 或 C++ 中,非零返回值通常用于表示错误。
    • 如果不写 return 语句的话,程序正常结束默认返回值也是 \(0\)

1.2 注释


作用

  • 可以用来解释程序的意思,还可以让某段代码不执行(但是依然保留在源文件里)。
    • 在代码中加一些说明和解释,方便自己或其他程序员阅读代码。

两种格式

  1. 单行注释// 描述信息
    • 通常放在一行代码的上方,或者一条语句的末尾,对该行代码说明。
  2. 多行注释/* 描述信息 */
    • 通常放在一段代码的上方,对该段代码做整体说明。

> 提示:编译器在编译代码时,会忽略注释的内容,注释对程序运行没有影响,C/C++通用该规则。

示例

cpp
#include <stdio.h>  //这是C语言的头文件
int main(){  //main()函数
    printf("Hello,world!");  //输出Hello,world!
    /*  这是多行注释,从本行开始
    printf("Hello,world!");  //输出Hello,world!
    截止至该行,以上的所有内容不会执行  */
    return 0;
}

1.3 数据的输入输出


输入

  • C 语法:scanf()
  • C++ 语法:cin >>

输出

  • C 语法:printf()
  • C++ 语法:cout &lt;&lt;

C语言代码示例

c
#include <stdio.h>

int main() {
    int a;  // 定义一个变量a
    scanf("%d", &a);  // 输入a的值
    printf("%d", a);  // 输出a的值
    return 0;
}

C++语言代码示例

cpp
#include <iostream>
using namespace std;

int main() {
    int a;  // 定义一个变量a
    cin >> a;  // 输入a的值
    cout << a;  // 输出a的值
    return 0;
}

关于变量

  • 可以简单理解为一个名字及其对应的值。
  • 变量有其相关的数据类型,如整型 int、双精度浮点型 double 等。

关于 scanf("%d", &a);

  • scanf() 是 C 语言的输入函数,"" 内为格式化字符串。
    • %d 表示读入一个有符号整型(int 型)的值。
    • &a 表示将读入的数据存入变量 a 的内存地址。

关于 printf("%d", a);

  • printf() 是 C 语言的输出函数,"" 内为格式化字符串。
    • %d 表示输出一个有符号整型(int 型)的值。
    • a 表示输出变量 a 的值。

关于 cin &gt;> a;

  • cin >> 是 C++ 语言的输入流操作符。
    • cin 在输入时会自动匹配变量的数据类型。

关于 cout &lt;&lt; a;

  • cout &lt;&lt; 是 C++ 语言的输出流操作符。
    • cout 在输出时会自动识别变量的数据类型。

2. 数据类型

2.1 变量

作用

  • 给一段指定的内存空间起名,方便操作这段内存。
  • 变量用于存储对应数据类型的数据。

语法

  • 数据类型 变量名;
  • 数据类型 变量名 = 初始值;

C语言代码示例

c
#include <stdio.h>

int main() {
    int a;  // 定义一个int型的变量a
    int b = 10;  // 定义一个int型的变量b,初始值为10
    scanf("%d", &a);
    printf("%d %d", a, b);  // 输出a的值后输出一个空格,再输出b的值
    return 0;
}

C++语言代码示例

cpp
#include <iostream>
using namespace std;

int main() {
    int a;  // 定义一个int型的变量a
    int b = 10;  // 定义一个int型的变量b,初始值为10
    cin >> a;
    cout << a << " " << b;  // 输出a的值后输出一个空格,再输出b的值
    return 0;
}

2.2 整型


作用

  • 用于存储整数。
    • C++ 标准中,规定 int 的位数至少为 16 位,事实上在现在的绝大多数平台,int 的位数均为 32 位。

对照表

数据类型占用空间取值范围
short2 字节\((-2^{15} \sim 2^{15}-1)\)
int4 字节\((-2^{31} \sim 2^{31}-1)\)
longWindows 为 4 字节,Linux 为 4 字节 (32 位),8 字节 (64 位)\((-2^{31} \sim 2^{31}-1)\)
long long8 字节\((-2^{63} \sim 2^{63}-1)\)
unsigned long long8 字节\((0\sim 2^{64}-1)\)

常用的整型输入和输出

  • int:对应 scanf() 读入和 printf() 输出应为 %d
  • long long:对应 scanf() 读入和 printf() 输出应为 %lld

C 语言代码示例

c
#include <stdio.h>

int main(){
    int a; // 定义一个int型的变量a
    long long b; // 定义一个long long型的变量b
    scanf("%d %lld", &a, &b); // 读入a和b的值
    printf("%d %lld", a, b); // 输出a的值后输出一个空格,再输出b的值
    return 0;
}

C++ 语言代码示例

cpp
#include <iostream>
using namespace std;

int main(){
    int a; // 定义一个int型的变量a
    long long b; // 定义一个long long型的变量b
    cin >> a >> b; // 读入a和b的值
    cout << a << " " << b; // 输出a的值后输出一个空格,再输出b的值
    return 0;
}

2.3 浮点型


作用

  • 存储浮点数。
    • float 保留 6 位小数,double 保留 15 位小数。

对照表

数据类型占用空间有效数字范围
float4 字节\((-3.4\times 10^{-38}\sim 3.4\times 10^{38})\)
double8 字节\((-1.7\times 10^{-308}\sim 1.7\times 10^{308})\)

常用的浮点型输入和输出

  • float:对应 scanf() 读入和 printf() 输出应为 %f

  • double:对应 scanf() 读入和 printf() 输出应为 %lf

  • printf() 在输出浮点数时,保留x位小数可以使用 %.xf%.xlf

  • cout &lt;&lt; 在输出浮点数时,保留 x 位小数,需要加上头文件 #include &lt;iomanip&gt; ,然后 cout &lt;&lt; setprecision(x)

C语言代码示例

cpp
#include <stdio.h>

int main(){
    float a;  //定义一个float型的变量a
    double b;  //定义一个double型的变量b
    scanf("%f %lf", &a, &b);  //读入a和b的值
    printf("%f %lf\n", a, b);  //输出a的值后输出一个空格,再输出b的值再输出回车
    printf("%.2f %.3lf\n", a, b);  //输出a的值保留2位小数后输出一个空格,再输出b的值保留3位小数再输出回车
    return 0;
}

C++语言代码示例

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

int main(){
    float a;  //定义一个float型的变量a
    double b;  //定义一个double型的变量b
    cin >> a >> b;  //读入a和b的值
    cout << a << " " << b << endl;  //输出a的值后输出一个空格,再输出b的值再输出回车
    cout << setprecision(2) << a << " " << setprecision(3) << b << endl;  //输出a的值保留2位小数后输出一个空格,再输出b的值保留3位小数再输出回车
    return 0;
}

了解更多:对于数字是如何在计算机中存储的,可以参考数字在计算机中的表示


2.4 字符型


作用

  • 存储单个字符。
    • 存储字符为其对应的ASCII编码。

语法

  • char 变量名;
    • char 变量名 = 'a';

注意

  • 在初始化字符型变量时,用单引号将字符括起来,不要用双引号。
    • 单引号内只能有一个字符如 bc 等,可以是空格符 ,但不可以是字符串如"abcd"
    • CC++ 中字符型变量只占用 \(1\) 个字节。

字符型输入和输出

  • char:对应 scanf()读入和 printf() 输出应为 %c

C语言代码示例

c
#include <stdio.h>

int main() {
    char a;  // 定义一个char型的变量a
    scanf("%c", &a);  // 读入a值
    printf("%c\n", a);  // 输出a的值后输出一个回车
    return 0;
}

C++语言代码示例

cpp
#include <iostream>
using namespace std;

int main() {
    char a;  // 定义一个char型的变量a
    cin >> a;  // 读入a值
    cout << a << endl;  // 输出a的值后输出一个回车
    return 0;
}

ASCII码表格

ASCII 值控制字符ASCII 值字符ASCII 值字符ASCII 值字符
0NUL32(空格)64@96`
1SOH33!65A97a
2STX34"66B98b
3ETX35#67C99c
4EOT36$68D100d
5ENQ37%69E101e
6ACK38&70F102f
7BEL39'71G103g
8BS40(72H104h
9HT41)73I105i
10LF42*74J106j
11VT43+75K107k
12FF44,76L108l
13CR45-77M109m
14SO46.78N110n
15SI47/79O111o
16DLE48080P112p
17DC149181Q113q
18DC250282R114r
19DC351383S115s
20DC452484T116t
21NAK53585U117u
22SYN54686V118v
23ETB55787W119w
24CAN56888X120x
25EM57989Y121y
26SUB58:90Z122z
27ESC59;91[123{
28FS60<92\124
29GS61=93]125}
30RS62>94^126~
31US63?95_127DEL

ASCII 码大致由以下两部分组成

  • ASCII 非打印控制字符:ASCII 表上的数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备。
  • ASCII 打印字符:数字 32-126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。

转义字符

作用

  • 用于表示一些不能显示出来的ASCII字符。
    • 现阶段我们常用的转义字符有:\n \\ \t
转义字符含义ASCII码值(十进制)
\n换行(LF) ,将当前位置移到下一行开头010
\t水平制表(HT) (跳到下一个TAB位置)009
\\代表一个反斜线字符\092
\'代表一个单引号(撇号)字符039

2.5 字符串类型


作用

  • 存储字符串。
    • 相当于C语言的字符数组。

语法

  • string 变量名;
    • string = "abcd";

注意

  • 该类型仅在C++中支持,使用须添加头文件 #include &lt;string&gt;
    • 在初始化字符串变量时,用双引号将字符串括起来,不要用单引号,即单个字符视为长度为 \(1\) 的字符串。
    • string 类型大小与其所含的字符长度有关。

字符串的输入和输出

  • string:使用 cin >>cout &lt;&lt; 直接输出即可。

C++语言代码示例

cpp
#include <iostream>
#include <string>
using namespace std;

int main(){
    string s1 = "a";  //定义一个string型的变量s1,初始化值为"a"
    string s2;        //定义一个string型的变量s2
    cin >> s2;        //读入s2的值
    cout << s1 << endl << s2;  //输出s1的值后输出一个回车,再输出s2
    return 0;
}

> 更多支持操作参见string 容器

C与C++表达字符串的对比

C语言代码示例

cpp
#include <stdio.h>

int main(){
    char a[100] = "lys is a doge";  //C语言可以借助字符数组来达到同样效果
    printf("%s\n",a);
    return 0;
}

C++语言代码示例

cpp
#include <iostream>
#include <string>
using namespace std;

int main(){
    string a = "lys is a doge";
    cout << a << endl;
    return 0;
}

2.6 布尔类型


作用

  • 代表 true (真) 或 false (假) 的值。
    • 数值上为 \(1\)\(0\)\(1\) 为真,\(0\) 为假。

语法

  • bool 变量名;
    • bool 变量名 = true;
    • bool 变量名 = 1;

注意

  • 该类型在 C++ 语言中支持,在 C 语言中须添加头文件 #include &lt;stdbool.h&gt; 使用。
    • bool 类型只有两个值,truefalse
    • bool 类型大小只有 \(1\) 字节。

布尔类型的输入和输出

  • boolcin >>cout &lt;&lt; 直接输出即可。
    • 输入时,只能输入 \(0\)\(1\)

C++语言代码示例

cpp
#include <iostream>
using namespace std;

int main(){
    bool a = false, b = 1;  //定义一个bool型的变量a,b,初始化值为false和1
    bool n;  //定义一个bool型的变量n
    cin >> n;  //读入n的值,只能是0或1
    cout << a << " " << b << endl;  //输出a的值后输出一个空格,再输出b的值和一个回车
    cout << n << endl;  //输出n的值和一个回车
    return 0;
}

2.7 常量


作用

  • 用于记录程序中不可更改的数据。

语法

  • const 数据类型 常量名 = 初始化值;
    • #define 常量名 常量值

注意

  • 常量是固定值,在程序执行期间不会改变。
    • 常量的值在定义后不能被修改,修改则会报错。

C语言代码示例

c
#include <stdio.h>

#define b 20  //宏定义的方式定义常量

int main(){
    const int a = 10;  //const 方式定义常量
    /*
    a = 20;  //a是常量不可修改,否则报错
    b = 10;  //b是常量不可修改,否则报错
    */
    printf("%d %d", a, b);
    return 0;
}

2.8 关键字及标识符命名规则


关键字

  • 关键字是 C/C++ 中预先保留的单词(标识符)。
    • 在定义变量或者常量时候,不要用关键字,否则会产生冲突。

C/C++关键字

asmdoifreturntypedef
autodoubleinlineshorttypeid
booldynamic_castintsignedtypename
breakelselongsizeofunion
caseenummutablestaticunsigned
catchexplicitnamespacestatic_castusing
charexportnewstructvirtual
classexternoperatorswitchvoid
constfalseprivatetemplatevolatile
const_castfloatprotectedthiswchar_t
continueforpublicthrowwhile
defaultfriendregistertrue
deletegotoreinterpret_casttry

命名规则

作用

  • C++规定给标识符(变量、常量)命名时,有一套自己的规则,避免和关键字发生冲突。

规则

  • 标识符不能是关键字。
  • 标识符只能由字母、数字、下划线组成。
  • 第一个字符必须为字母或下划线。
  • 标识符中字母区分大小写。

2.9 类型转换


作用

  • 在一些时候(比如某个函数接受 int 类型的参数,但传入了 double 类型的变量),我们需要将某种类型转换成另外一种类型。
  • C++ 中类型的转换机制较为复杂,这里主要介绍对于基础数据类型的两种转换:数值提升和数值转换。

2.9.1 数值提升


注意

  • 数值提升过程中,值本身保持不变。

转换规则

  • char 类型和 short 类型在进行算术运算时会自动提升为 int 类型。类似地,unsigned short 类型在进行算术运算时会自动提升为 unsigned int 类型。
  • 如果有必要(例如向一个接受 long long 类型参数的函数中传入 int 类型的变量),可以将位宽较小的整型变量提升为位宽较大的整型变量(注意符号性需保持不变,若符号性改变,则发生数值转换)。一个常见情况是:位宽较小的变量与位宽较大的变量进行算术运算时,会先将位宽较小的变量提升为位宽较大的变量。
  • 位宽较小的浮点数可以提升为位宽较大的浮点数(例如 float 类型的变量和 double 类型的变量进行算术运算时,会将 float 类型变量提升为 double 类型变量),其值不变。
  • bool 类型可以提升为整型,false 变为 \(0\),而 true 对应为 \(1\)

2.9.2 数值转换


注意

  • 数值转换过程中,值可能会发生改变。

转换规则

如果目标类型为位宽为 \(16\) 的无符号整数类型,则转换结果可以认为是原值取模 \(2^{16}\) 后的结果。例如,将 short 类型的值 \(-1\)(二进制表示为 \(1111~1111~1111~1111\))转换为 unsigned int 类型,其值为 \(65535\)(二进制表示为 \(0000~0000~0000~0000~1111~1111~1111~1111\))。

  • 如果目标类型为位宽为 \(x\) 的带符号整数类型,则 一般情况下 ,转换结果可以认为是原值 \(mod~2^x\) 后的结果。例如将 unsigned int 类型的值 \(4~294~967~295\)(二进制表示为 \(1111~1111~1111~1111~1111~1111~1111~1111\))转换为 short 类型,其值为 \(-1\)(二进制表示为 \(1111~1111~1111~1111\))。
  • 位宽较大的浮点数转换为位宽较小的浮点数,会将该数舍入到目标类型最接近的值。
  • 浮点数转换为整数时,会舍弃浮点数的全部小数部分。
  • 整数转换为浮点数时,会舍入到目标类型最接近的值。
  • 将其他类型转换为 bool 类型时,零值转换为 false,非零值转换为 true

3. 运算符


3.1 算术运算符


作用

  • 用于处理四则运算。

算术运算符包括以下符号:

运算符术语示例结果
+正号+33
-负号-3-3
+10 + 515
-10 - 55
*10 * 550
/10 / 52
%取模(取余数)10 % 31
++前置递增a = 2; b = ++a;a = 3; b = 3;
++后置递增a = 2; b = a++;a = 3; b = 2;
--前置递减a = 2; b = --a;a = 1; b = 1;
--后置递减a = 2; b = a--;a = 1; b = 2;

注意

  • 在除法运算中,除数不能为 \(0\)
  • 只有整型变量可以进行取模运算。
  • 前置递增先对变量进行 ++,再计算表达式,后置递增相反。

算术运算符中的类型转换

作用

  • 当参与运算的变量的数据类型不同时,会发生类型转换,使得两个变量类型一致后再进行运算。

转换规则

  • 先将 charboolshort 等类型提升至 int(或 unsigned int,取决于原类型的符号性)类型;
  • 否则,若存在一个变量类型为 double,会将另一变量转换为 double 类型;
  • 否则,若存在一个变量类型为 float,会将另一变量转换为 float 类型;
  • 否则(即参与运算的两个变量均为整数类型):
    • 若两个变量符号性一致,则将位宽较小的类型转换为位宽较大的类型;
    • 否则,若无符号变量的位宽不小于带符号变量的位宽,则将带符号数转换为无符号数对应的类型;
    • 否则,若带符号操作数的类型能表示无符号操作数类型的所有值,则将无符号操作数转换为带符号操作数对应的类型;
    • 否则,将带符号数转换为相对应的无符号类型。

示例

对于一个整型(int)变量 x 和另一个双精度浮点型(doubley 类型变量:

  • x / 3 的结果将会是整型;
  • x / 3.0 的结果将会是双精度浮点型;
  • x / y 的结果将会是双精度浮点型;
  • x * 1 / 3 的结果将会是整型;
  • x * 1.0 / 3 的结果将会是双精度浮点型;

3.2 赋值运算符


作用

  • 用于将表达式的值赋给变量。

赋值运算符包括以下几个符号:

运算符术语示例结果
=赋值a = 2; b = 3;a = 2; b = 3;
+=加等于a = 0; a += 2;a = 2;
-=减等于a = 5; a -= 3;a = 2;
*=乘等于a = 2; a *= 2;a = 4;
/=除等于a = 4; a /= 2;a = 2;
%=模等于a = 3; a %= 2;a = 1;

对于复合赋值运算符 +=:例如 a = 0; a += 2;,等价为 a = 0; a = a + 2,结果 a = 2,其余复合赋值运算符的运算同理。


3.3 比较运算符


作用

  • 用于表达式的比较,并返回一个真值或假值。

比较运算符有以下符号:

运算符术语示例结果
==相等于4 == 30
!=不等于4 != 31
&lt;小于4 &lt; 30
&gt;大于4 > 31
&lt;=小于等于4 &lt;= 30
&gt;=大于等于4 >= 11

注意

  • 要将等于运算符 == 和赋值运算符 = 区分开来,这在判断语句中尤为重要。
    • 例如:if (op = 1)if (op == 1) 看起来类似,但实际功能却相差甚远。第一条语句是在对 op 进行赋值,若赋值为非 0 时为真值,表达式的条件始终是满足的,无法达到判断的作用;而第二条语句才是对 op 的值进行判断。

3.4 逻辑运算符


作用

  • 用于根据表达式的值返回真值或假值。

逻辑运算符有以下符号:

运算符术语示例结果
!!a如果 a 为假,则 !a 为真;如果 a 为真,则 !a 为假。
&&a && b如果 a 和 b 都为真,则结果为真,否则为假。
||a || b如果 a 和 b 有一个为真,则结果为真,二者都为假时,结果为假。

C++语言代码示例

cpp
#include <iostream>
using namespace std;

int main() {
    bool res;
    bool a, b;
    cin >> a >> b;

    res = a && b;  // 当 a 与 b 都为真时则 res 为真
    cout << res << endl;

    res = a || b;  // 当 a 或 b 其中一个为真时则 res 为真
    cout << res << endl;

    res = !a;      // 当 a 为假时则 res 为真
    cout << res << endl;

    return 0;
}

3.5 位运算符


作用

  • 位运算就是基于整数的二进制表示进行的运算。
    • 由于计算机内部就是以二进制来存储数据,位运算是相当快的。

运算符及运算规则

运算运算符数学符号解释
&&只有两个对应位都为 1 时才为 1
||只要两个对应位中有一个为 1 时就为 1
取反~~按位取反,即对应的 0 变为 1,1 变为 0
异或^只有两个对应位不同时才为 1

运算示例

cpp
// 与运算
0 & 0 = 0;
0 & 1 = 0;
1 & 0 = 0;
1 & 1 = 1;

// 或运算
0 | 0 = 0;
0 | 1 = 1;
1 | 0 = 1;
1 | 1 = 1;

// 取反运算
~0 = 1;
~1 = 0;

// 异或运算
0 ^ 0 = 0;
0 ^ 1 = 1;
1 ^ 0 = 1;
1 ^ 1 = 0;

// 右移运算
n >> k  // 表示 n / 2^k

// 左移运算
n << k  // 表示 n * 2^k

常用模板

cpp
// 求 x 的第 k 位数字
x >> k & 1;

// 求 x 的最后一位 1
x & -x;

取反及补码

  • 取反是对一个数 num 进行的位运算,即单目运算。

  • 取反暂无默认的数学符号表示,其对应的运算符为 ~。它的作用是把 \(num\) 的二进制补码中的 \(0\)\(1\) 全部取反( \(0\) 变为 \(1\)\(1\) 变为 \(0\) )。有符号整数的符号位在 ~ 运算中同样会取反。

  • 补码:在二进制表示下,正数和 \(0\) 的补码为其本身,负数的补码是将其对应正数按位取反后加一。

  • 例如:

\[\begin{aligned} 5 &= (00000101)_2 \\ \sim 5 &= (11111010)_2 = -6 \\ \text{补码}(-5) &= (11111011)_2 \\ \sim(-5) &= (00000100)_2 = 4 \end{aligned} \]

异或运算的逆运算

  • 异或运算的逆运算是它本身,也就是说两次异或同一个数最后结果不变,即 \(a\oplus b \oplus b = a\)

  • 例如:

\[\begin{aligned} 5 &= (00000101)_2 \\ 6 &= (00000110)_2 \\ 5\ \&\ 6 &= (00000100)_2 = 4 \\ 5\ |\ 6 &= (00000111)_2 = 7 \\ 5\oplus 6 &= (00000011)_2 = 3 \end{aligned} \]

左移与右移

  • num &lt;&lt; i 表示将 \(num\) 的二进制表示向左移动 \(i\) 位所得的值。

  • num &gt;> i 表示将 \(num\) 的二进制表示向右移动 \(i\) 位所得的值。

  • 例如:

\[\begin{aligned} 11 &= (00001011)_2 \\ 11 &lt;&lt; 3 &= (01011000)_2 = 88 \\ 11 &gt;> 2 &= (00000010)_2 = 2 \end{aligned} \]

注意

  • 移位运算中如果出现如下情况,则其行为未定义:
    • 右操作数(即移位数)为负值。
    • 右操作数大于或等于操作数类型的位数。
    • 对于左移操作,需要确保移位后的结果能被原数的类型容纳,否则行为也是未定义的。对一个负数执行左移操作也未定义。
    • 对于右移操作,右侧多余的位将会被舍弃,而左侧较为复杂:对于无符号数,会在左侧补 \(0\) ;而对于有符号数,则会用最高位的数(其实就是符号位,非负数为 \(0\),负数为 \(1\))补齐。
    • 位运算的优先级低于算术运算符 (除了取反),而按位与、按位或及异或低于比较运算符。

3.6 逗号运算符

作用

  • 逗号运算符 , 可将多个表达式分隔开来,被分隔开的表达式按从左至右的顺序依次计算,整个表达式的值是最后的表达式的值。

注意

  • 逗号表达式的优先级在所有运算符中的优先级是最低的,即再进行多个表达式运算时,在所有其他运算符运算完毕后才执行逗号运算符的运算。

C++代码示例

cpp
#include <iostream>
using namespace std;

int main() {
    int res;
    res = 1 + 2, 3 + 4, 5 + 6;
    // 得到 res 的值为 3 而不是 11,因为赋值运算符 "=" 的优先级比逗号运算符高,先进行了赋值运算才进行逗号运算。
    cout << res << endl;
    res = (1 + 2, 3 + 4, 5 + 6);
    // 若要让 res 的值得到逗号运算的结果,则应将整个表达式用括号提高优先级,此时 res 的值才为 11。
    cout << res << endl;
    return 0;
}

3.7 运算符优先级


作用

  • 不同的运算符在表达式中的优先级不同,会使得表达式按一定的运算规则进行运算,使得运算不发生歧义。

注意

  • 优先级高的运算符先运算,优先级低的运算符后运算(如逗号运算符的例子)。
    • 优先级和结合性是编译时概念,与求值顺序无关,后者是运行时概念。

优先级对照

取自 C++ 运算符优先级 - cppreference


4. 程序流程结构


C/C++ 支持最基本的三种程序运行结构

  • 顺序结构:程序按顺序执行,不发生跳转。
  • 选择结构:依据条件是否满足,有选择地执行相应功能。
  • 循环结构:依据条件是否满足,循环多次执行某段代码。

4.1 选择结构


4.1.1 if 语句


作用

  • 执行满足条件的语句。

语法

基本 if 语句

cpp
if(条件表达式){
    // 语句
}

C 语言代码示例

cpp
#include <stdio.h>

int main(){
    int a;  // 定义一个 int 型变量 a
    scanf("%d", &a);  // 读入 a 的值
    if(a != 0){  // 如果 a 的值不为 0
        printf("%d\n", a);  // 输出 a 的值并输出一个回车
    }
    // 若 a 的值为 0,则不会执行任何内容,程序正常退出
    return 0;
}

if...else 语句

cpp
if(条件表达式){
    // 语句 1
}
else{
    // 语句 2
}

C 语言代码示例

cpp
#include <stdio.h>

int main(){
    int a;  // 定义一个 int 型变量 a
    scanf("%d", &a);  // 读入 a 的值
    if(a != 0){  //如果a的值不为0
        printf("%d\n", a);  //输出a的值并输出一个换行
    }
    else{
        printf("a的值为0!\n");  //若a的值为0,输出"a的值为0!"并输出一个换行
    }
    return 0;
}

else if 语句

c
if(条件表达式1){
    //语句1
}
else if(条件表达式2){
    //语句2
}
else{
    //语句3
}

C语言代码示例

c
#include <stdio.h>

int main(){
    int a;  //定义一个int型变量a
    scanf("%d", &a);  //读入a的值

    if(a > 100){  //如果a的值大于100
        printf("%d\n", a - 100);  //输出a - 100的值并输出一个换行
    }
    else if(a > 10){
        printf("%d\n", a - 10);  //输出a - 10的值并输出一个换行
    }
    else{
        printf("%d\n", a);  //输出a的值并输出一个换行
    }
    return 0;
}

三目运算符(条件表达式)

语法表达式1 ? 表达式2 : 表达式3;

解释

  • 如果表达式 1 的值为真,执行表达式 2,并返回表达式 2 的结果。
  • 如果表达式 1 的值为假,执行表达式 3,并返回表达式 3 的结果。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    int a;  // 定义一个int型变量a
    scanf("%d", &a);  // 读入a的值
    int c = a > 0 ? a : -a;  // 若a的值大于0则c = a,否则c = -a
    printf("%d\n", c);
    return 0;
}

4.1.3 switch语句


作用

  • 执行多条件分支语句。

语法

cpp
switch (条件表达式) {
    case 结果1: 执行语句1; break;
    case 结果2: 执行语句2; break;
    // ...
    default: 执行语句n; break;
}

注意

  • switch 语句执行时,先求出条件表达式的值,然后根据该值选择相应的 case 常量,从该 case 处开始执行。
    • 其中,条件表达式必须是一个整数类型表达式,而 case 后的常量都必须是整数类型的常量。
    • switch 语句中还要根据需求加入 break 语句进行中断,否则在对应的 case 被选择之后,接下来的所有 case 里的语句和 default 里的语句都会被运行。
    • 当无对应的 case 条件时,会统一执行 default 的语句。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    // 请给电影评分
    // 10 ~ 9   经典
    // 8 ~ 7    非常好
    // 6 ~ 5    一般
    // 5分以下  烂片

    int score;
    scanf("%d", &score);  // 输入分数

    switch (score) {
        case 10:
        case 9:
            printf("经典\n");
            break;
        case 8:
            printf("非常好\n");
            break;
        case 7:
        case 6:
            printf("一般\n");
            break;
        default:
            printf("烂片\n");
            break;
    }

    return 0;
}

4.2 循环结构


4.2.1 while循环语句


作用

  • 满足循环条件,执行循环语句。

语法

cpp
while (条件表达式) {
    // 循环体语句
}

解释

  • 先判断循环条件表达式,只要循环条件的结果为真,就执行循环语句。

注意

  • 在执行循环语句时,程序必须提供跳出循环的出口,否则会出现死循环。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    int sum = 1;
    while(sum <= n) {  //当sum小于等于n时执行
        printf("%d\n", sum);  //输出sum的值和回车
        sum++;  //执行完一次循环,sum的值加1
    }
    return 0;
}

4.2.2 do...while循环语句


作用

  • 满足循环条件,执行循环语句。

语法

cpp
do {
    //语句
} while(条件表达式);

解释

  • 先执行一次语句,再判断条件表达式,只要条件表达式的结果为真,就执行循环语句。

注意

  • while 的区别在于 do...while 会先执行一次循环语句,再判断循环条件。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    int sum = 10;
    do {
        printf("%d\n", sum);  //输出sum的值和回车
        sum--;  //执行完一次循环,sum的值减1
    } while(sum >= n);  //当sum大于等于n时执行
    //若输入的n值大于10,则仍会先执行一次语句,再判断循环条件
    return 0;
}

4.2.3 for循环语句


作用

  • 先判断条件表达式,满足循环条件,执行循环语句。

语法

cpp
for(初始化条件; 判断条件; 更新) {
    //语句
}

注意

  • for 循环中的表达式,要用分号进行分隔。
    • for 语句的三个部分中,任何一个部分都可以省略。
    • 其中,若省略了判断条件,相当于判断条件永远为真。

解释

  • 只要循环条件的结果为真,就执行循环语句。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){  //当i小于等于n时,执行循环体内的语句,执行之后,i的值加1
        printf("%d\n", i);  //输出i的值和一个回车
    }
    return 0;
}

4.2.4 嵌套循环


作用

  • 在循环体中再嵌套一层循环,解决一些实际问题。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){  //当i小于等于n时,执行循环体内的语句,执行之后,i的值加1
        for(int j = 1; j <= i; j ++){//当j小于等于i时,执行循环体内的语句,执行之后,j的值加1
            printf("%d ", j);  //输出j的值
        }
        printf("\n");  //执行完内层的循环之后,输出一次回车
    }
    return 0;
}

4.3 跳转语句


4.3.1 break语句


作用

  • 出现在switch条件语句中,作用是终止case并跳出switch
  • 出现在循环语句中,作用是跳出当前的循环语句。
  • 出现在嵌套循环中,跳出最近的内层循环语句。

C语言代码示例1

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){  //当i小于等于n时,执行循环体内的语句,执行后i的值加1
        printf("%d ", i);  //输出i的值和一个空格
        if(i == 10){  //若i的值为10,则终止循环
            break;
        }
    }
    //当n小于等于10,会输出1 2 3 ... n
    //当n大于10时,会输出1 2 3 ... 10
    return 0;
}

C语言代码示例2

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){  //当i小于等于n时,执行循环体内的语句,执行后i的值加1
        for(int j = 1; j <= i; j ++){  //当j小于等于i时,执行循环体内的语句,执行之后,j的值加1
            printf("%d ", j);  //输出j的值
            if(j == 5){  //j的值为5时,退出该层循环
                break;
            }
        }
    }
    return 0;
}

4.3.2 continue语句


作用

  • 在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。

注意

  • continue 并没有使整个循环终止,而 break 会跳出循环。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){  //当i小于等于n时,执行循环体内的语句,执行后i的值加1
        if(i % 2 == 0){  //当i为偶数时,跳过本次循环,不再执行下面的语句
            continue;
        }
        printf("%d ", i);  //输出i的值和一个空格
    }
    //执行后会输出所有不大于n的所有奇数
    return 0;
}

5. 数组


5.1 概述


  • 所谓数组,就是一个集合,里面存放了相同数据类型 的元素。
    • 数组是存放相同类型对象的容器,数组中存放的对象没有名字,而是要通过其所在的位置访问。
    • 数组是由连续的内存位置组成的,数组的大小是固定的,不能随意改变数组的长度。

> 程序 = 逻辑 + 数据,数组是存储数据的强而有力的手段。 ——闫学灿


5.2 一维数组


5.2.1 一维数组定义方式


语法

  • 数据类型 数组名[数组长度];
    • 数据类型 数组名[数组长度] = {值1, 值2, ...};
    • 数据类型 数组名[] = {值1, 值2, ...};

解释

  • 数组的声明形如 int a[d],其中,int 说明该数组是 int 类型的数组,里面存放的所有元素均为 int 类型,a 是数组的名字,d 是数组中元素的个数。

  • C99 之前:元素数量必须是编译时确定的字面量,C99 开始我们已经可以用变量来定义数组的大小。

  • 示例:

cpp
int a[100];
int d1 = 100;
const int d2 = 100;
int a1[d1];  // 正确:C99支持该定义方式
int a2[d2];  // 正确:a2 是一个长度为 100 的数组

关于C99

  • C99 标准是 ISO/IEC 9899:1999 - Programming languages -- C 的简称,是 C 语言的官方标准第二版。

注意

  • 不能将一个数组直接赋值给另一个数组。
  • 示例:
cpp
int a1[3];
int a2[3] = a1[3];  // 错误
int a3[3];
a3 = a1;  // 错误

应该尽量将较大的数组定义为全局变量,即将数组声明在 main() 函数外的全局区。因为局部变量会被创建在栈区中,过大(大于栈的大小)的数组会爆栈,进而导致 RE。如果将数组声明在全局作用域中,就会在静态区中创建数组。

cpp
#include <stdio.h>

int main(){
    int a[100000000];  //数组过大,导致爆栈,运行错误,使得程序异常退出
    printf("数组定义成功");
    return 0;
}
cpp
#include <stdio.h>

int a[100000000];  //数组建立在全局区,程序正常运行

int main(){
    printf("数组定义成功");
    return 0;
}

5.2.2 一维数组元素访问


语法

  • 数组名[下标];

注意

  • 数组的索引(即方括号中的值)从 \(0\) 开始,直到数组长度-1。
    • 以一个包含 \(10\) 个元素的数组为例,它的索引为 \(0\)\(9\),而非 \(1\)\(10\)
    • 若访问下标越界,则会发生段错误。

C语言代码示例

cpp
#include <stdio.h>

int main() {
    //定义方式1
    //数据类型 数组名[元素个数];
    int score1[10];

    //利用下标赋值
    score1[0] = 100;
    score1[1] = 99;
    score1[2] = 85;

    //利用下标输出
    printf("%d\n", score1[0]);
    printf("%d\n", score1[1]);
    printf("%d\n", score1[2]);

    //第二种定义方式
    //数据类型 数组名[元素个数] =  {值1,值2 ,值3 ...};
    //如果{}内不足10个数据,剩余数据用0补全
    int score2[10] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };

    //一个一个输出太麻烦,因此可以利用循环进行输出
    for (int i = 0; i < 10; i++){
        printf("%d ", score2[i]);
    }
    printf("\n");

    //定义方式3
    //数据类型 数组名[] =  {值1,值2 ,值3 ...};
    int score3[] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };

    for (int i = 0; i < 10; i++){
        printf("%d ", score3[i]);
    }
    printf("\n");
    return 0;
}

C++语言代码示例

cpp
#include <iostream>
using namespace std;

int main() {

    //定义方式1
    //数据类型 数组名[元素个数];
    int score1[10];

    //利用下标赋值
    score1[0] = 100;
    score1[1] = 99;
    score1[2] = 85;

    //利用下标输出
    cout << score1[0] << endl;
    cout << score1[1] << endl;
    cout << score1[2] << endl;

    //第二种定义方式
    //数据类型 数组名[元素个数] = {值1, 值2, 值3...};
    //如果{}内不足10个数据,剩余数据用0补全
    int score2[10] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };

    //逐个输出
    //cout << score2[0] << " " << endl;
    //...
    //cout << score2[9] << " " << endl;

    //一个一个输出太麻烦,因此可以利用循环进行输出
    for (int i = 0; i < 10; i++) {
        cout << score2[i] << " ";
    }
    cout << endl;

    //定义方式3
    //数据类型 数组名[] = {值1, 值2, 值3...};
    int score3[] = { 100, 90, 80, 70, 60, 50, 40, 30, 20, 10 };

    for (int i = 0; i < 10; i++) {
        cout << score3[i] << " ";
    }
    cout << endl;

    return 0;
}

5.2.3 一维数组数组名


作用

  • 不同数组名表示不同数组。
  • 可以统计整个数组在内存中的长度。
  • 可以获取数组在内存中的首地址。

注意

  • 数组名的命名规范与变量名命名规范一致,不要和变量重名。

示例

cpp
#include <iostream>
using namespace std;

int main() {

    //数组名用途
    //1、可以获取整个数组占用内存空间大小
    int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    cout << "整个数组所占内存空间为: " << sizeof(a) << endl;  //sizeof() 关键字可以返回对应参数的大小
    cout << "每个元素所占内存空间为: " << sizeof(a[0]) << endl;
    cout << "数组的元素个数为: " << sizeof(a) / sizeof(a[0]) << endl;

    //2、可以通过数组名获取到数组首地址
    cout << "数组的首元素值为:" << *a << endl;  //*a 表示解引用,具体参见指针相关内容
    cout << "数组首地址为: " << &a << endl;  //&a 表示取数组a的首元素的地址,默认输出为16进制
    cout << "数组中第一个元素地址为: " << &a[0] << endl;
    cout << "数组中第二个元素地址为: " << (long long)&a[1] << endl;  //输出地址为10进制

    //arr = 100; 错误,数组名是常量,因此不可以赋值

    return 0;
}

总结

  • 数组名是常量,不可以赋值。
  • 直接打印数组名,可以查看数组所占内存的首地址。
  • 对数组名进行 sizeof,可以获取整个数组占内存空间的大小。

5.3 二维数组及多维数组


  • 多维数组的实质是数组的数组,即外层数组的元素是数组。
    • 二维数组就是在一维数组上,多加一个维度。
    • 多维数组定义及访问同二维数组的定义,只是在其基础上再加一个维度。

5.3.1 二维数组定义方式


语法

  • 数据类型 数组名[行数][列数];
    • 数据类型 数组名[行数][列数] = { {数据1, 数据2}, {数据3, 数据4} };
    • 数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4 };
    • 数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4 };

> TIPS:以上4种定义方式,第二种更加直观,提高代码的可读性。

解释

  • 数组的声明形如 int Array[3][4],那就指的是定义了一个三行四列的矩阵形状的二维数组,如下图所示。
    • 这样的矩阵在内存中是以箭头右边的方式存放的,也就是说实际上我们定义的二维数组在内存中仍然像是一维数组那样连续存储的,可以想象为把一个矩阵一层层伸展铺平。

5.3.2 二维数组元素访问


语法

  • `数组