C++: 基础
- TAGS: C++
环境安装
Windows系统
https://visualstudio.microsoft.com/zh-hans/
社区版:Visual Studio Community
- 选择安装 C++桌面开发
- 修改安装位置
Tab改为空格,通过设置菜单修改
- 在Visual Studio 中,打开“工具” > “选项”对话框。
- 导航到“文本编辑器” > “C/C++” > “制表符”(或者您正在使用的语言对应的选项卡)。
- 选择“插入空格”选项。
- 还可以在此对话框中调整制表符大小
Mac系统
App Store中搜索Xcode安装即可。
gcc --version g++ --version
实际上Xcode上我们用的是命令行开发。 不安装Xcode,可以直接安装命令行工具
- 打开苹果开发者官网 https://developer.apple.com/
- 找到Develop点击Downloads,点击More。中间可能需要账号登录。即访问 https://developer.apple.com/download/all/
- 搜索 command line tools,找到正式版,如 Command Line Tools for Xcode 26。点击View Details
- 安装即可。
IDE工具:vs code 安装
- 访问 https://code.visualstudio.com/ 点击 other platforms,下载M芯片 Apple silicon。其中默认下载的Universal版本兼容多平台,有冗余程序。
- 下载好,将app拖进应用程序。
初学C++
第一个程序
#include <iostream> using namespace std; int main() { cout << "11111" << endl << endl << endl; return 0; }
命名空间
- 命名空间就像是一个容器或者一个独立的区域。 用来组织和管理代码中的各种名称。想象一下,有很 多程序员都在编写代码,很可能会出现不同人定义了 相同名称的情况,如果没有命名空间,这些同名的名 字就有可能会产生冲突,导致程序出现错误,而命名 空间的存在可以有效地避免这种冲突。
std 命名空间
标准C++ 库中的所有内容,都在一个名为"std"的命名空间中,当你写下"using namespace std;"时,就是告诉编译器你要使用标准库中这个命名空间里的内容,这样就可以方便地调用标准库中的东西。
std::cout
符号
- int 整数
- main 主函数
- return 返回
- cout 输出
- include 包含
- iostream 输入输出流
- endl 换行 end line
- using 使用
- namesapce 命名空间
- std 标签
多练习,之后的每个代码例子先写框架,然后在框架内写实现。
#include <iostream> using namespace std; int main() { return 0; }
exe写入错误
#include <iostream> using namespace std; int main() { cout << "11111" << endl << endl << endl; system("pause"); return 0; }
运行
#include <iostream> using namespace std; int main() { cout << "11111" << endl << endl << endl; cout << "22222" << endl << endl << endl; system("pause"); return 0; }
修改后再运行,报错。Link ERROR
1>LINK : fatal error LNK1168: 无法打开 D:\project\cppproj\03_1\x64\Debug\03_1.exe 进行写入 1>已完成生成项目“03_1.vcxproj”的操作 - 失败。
打开了exe程序,再写入就写入失败。
解决:把之前打开的程序关闭,再运行代码就可以了。
main函数拼写错误
#include <iostream> using namespace std; int mian() { cout << "11111" << endl << endl << endl; cout << "22" << endl << endl << endl; system("pause"); return 0; }
执行报错
1>MSVCRTD.lib(exe_main.obj) : error LNK2019: 无法解析的外部符号 main,函数 "int __cdecl invoke_main(void)" (?invoke_main@@YAHXZ) 中引用了该符号 1>D:\project\cppproj\03_1\x64\Debug\03_1.exe : fatal error LNK1120: 1 个无法解析的外部命令
无法解析的外部符号 main
main函数重定义问题
同一个项目中有2个main函数就会报 Link error
1>main.obj : error LNK2005: main 已经在 m2.obj 中定义 1>D:\project\cppproj\03_1\x64\Debug\03_1.exe : fatal error LNK1169: 找到一个或多个多重定义的符号
新手经典报错合集
从第1条报错开始
警告
warning. 能解决的尽量解决。
注释
#include <iostream> using namespace std; int main() { // 单行注释 /* 多行注释 22 33 */ cout << "11111" << endl; return 0; }
变量
变量的定义与使用
#include <iostream> using namespace std; int main() { // 变量的定义 // 数据类型 变量名 = 初始值; int x = 1314 + 6; cout << "x = " << x << endl; /* int v; // 变量没有初始化,如果用它了就会报错 cout << "v = " << v << endl;*/ return 0; }
常量
#include <iostream> using namespace std; #define X "你好" #define Y "世界" int main() { cout << X << endl << Y << endl; return 0; }
#include <iostream> using namespace std; #define X 1+2 int main() { cout << X * X << endl; // 相当于,把X替换为1+2,输出为5 cout << 1 + 2 * 1 + 2 << endl; return 0; }
加法优先级小于乘法。
#include <iostream> using namespace std; #define X (1+2) int main() { cout << X * X << endl; // 相当于,把X替换为(1+2),输出为9 cout << (1 + 2) * (1 + 2) << endl; return 0; }
const int x = 7; // x = x + 1; // 常量不可以被修改
关键字
| asm | do | if | return | typedef |
| auto | double | inline | short | typeid |
| bool | dynamic_cast | int | signed | typename |
| break | else | long | sizeof | union |
| case | enum | mutable | static | unsigned |
| catch | explicit | namespace | static_cast | using |
| char | export | new | struct | virtual |
| class | extern | operator | switch | void |
| const | false | private | template | volatile |
| const_cas | float | protected | this | wchar_t |
| continue | for | public | throw | while |
| default | friend | register | true | |
| delete | goto | reinterpret_cast | try |
标识符
int obj;
obj是一个标识符,标识符是自定义的,不以数字符号开头。
- 区分大小定
- 长度尽量控制在31个字符以内
- 不能与关键字相同
数据类型
整型
#include <iostream> using namespace std; /* short 2字节 2^16 (-2^15 -> 2^15-1) (-32768 -> 3267) int 4 2^32 (-2^31 -> 2^31-1) long win 4; linux 4或8 long long 8 2^64 (-2^63 -> 2^63-1) */ int main() { short a = 32768; //整数溢出。想像成从-32768到32767是一个环 int b = 1; long c = 1; long long d = 1; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; cout << "d = " << d << endl; return 0; }
执行结果
a = -32768 b = 1 c = 1 d = 1
sizeof
获取数据类型占用的字节数。
#include <iostream> using namespace std; int main() { short a = 1; int b = 1; long c = 1; long long d = 1; cout << "short size = " << sizeof(short) << endl; cout << "int size = " << sizeof(int) << endl; cout << "long size = " << sizeof(long) << endl; cout << "long long size = " << sizeof(long long) << endl; return 0; } /* short size = 2 int size = 4 long size = 4 long long size = 8 */
还可以把对应变量修进来,如sizeof(a)
浮点型
float 精度不够问题
#include <iostream> #include <iomanip> #include <cmath> using namespace std; /* float 4字节 double 8字节 */ #define eps 1e-7 int main() { float a = 3.1415926f; double b = 3.1415926; double c = 1.5e5; //1.5*10^5 double d = 1.5e-5; //1.5*10^-5 cout << a << endl; cout << b << endl; cout << c << endl; cout << d << endl; cout << eps << endl; // 输出前做精度控制 cout << setprecision(10) << a << endl; cout << setprecision(10) << b << endl; double x = 1.0 / 2343242477777777773 * 2343242477777777773; // fabs 浮点数之间的比较。下面给出x-1小于eps时,认为x是等于1的。 if (fabs(x - 1) < eps) { cout << "hahah" << endl; } cout << x << endl; return 0; }
3.14159 3.14159 150000 1.5e-05 1e-07 3.141592503 // float精度误差 3.1415926 hahah 1
字符型
字符是用单引号括起来的。占用1个字节,用asccii码表整数表示。
#include <iostream> using namespace std; int main() { char a = 'y'; // 字符是用单引号括起来的。 char b = 'z'; cout << a << endl; cout << (int)a << endl; // 转换成整数 cout << (int)b << endl; cout << b - a << endl; b = 120; cout << b << endl; cout << sizeof(a) << endl; return 0; }
转义字符
#include <iostream> using namespace std; int main() { char a = '\n'; char b = '\t'; cout << (int)a << endl; cout << "abc" << a; // \n是一个换行符 cout << "abc\tdef"; // \t 反斜杠 cout << "abc\\def\n"; // \\ 变成1个杠 cout << "abc\0def" << endl; // \0表示字符串结束 cout << "abc\088def" << endl; cout << "abc\077def" << endl; cout << "aa\"bb" << endl; // 字符串中加引号 return 0; } /* \0xx 8进制 \077 表示? \088 8进制中没有8,所以是\0 */
10 abc abc defabc\def abc abc abc?def aa"bb
字符串
字符串的输出
#include <iostream> #include <string> using namespace std; /* char 变量名[] = ""; */ int main() { // c 风格 char a[] = "你好啊"; cout << sizeof(a) << endl; // c++风格 string b = "你好"; cout << b + "世界" << endl; return 0; }
布尔类型
#include <iostream> using namespace std; int main() { bool flag1 = false; // 0 假 bool flag2 = true; // 1 真 cout << flag1 << endl << flag2 << endl; flag1 = !flag1; // !表示取反 cout << flag1 << endl << flag2 << endl; cout << sizeof(flag1) << endl; return 0; }
数据的输入
#include <iostream> // i input // o output #include <string> using namespace std; int main() { // 1. 整型的输入 int a = 5; cin >> a; cout << "a的值变成了" << a << endl; // 2. 浮点型的输入 double b = 7; cin >> b; cout << "b的值变成了" << b << endl; // 3. 字符型的输入 char c = 7; cin >> c; cout << "c的值变成了" << c << endl; // 4. 字符串型 string d = ""; cin >> d; cout << "d的值变成了" << d << endl; // 5. 布尔型 bool e = false; cin >> e; cout << "e的值变成了" << e << endl; return 0; }
12 a的值变成了12 6.22 b的值变成了6.22 a c的值变成了a akdjf d的值变成了akdjf 4 e的值变成了1
unsigned
size() 的溢出问题
运算符
算术运算符
加减乘除运算符
#include <iostream> using namespace std; int main() { int a = 6; int b = 9; cout << a + b << endl; cout << a - b << endl; cout << a * b << endl; cout << a / b << endl; cout << a * 1.0 / b << endl; a = 100000000; b = 10000000; cout << a * b << endl; // 整型溢出问题 cout << (long long)a * b << endl; a = -1; b = 2; cout << a / b << endl; // 输出为0 不是向下取整,而是把小数部分截断 a = +6; // 6 b = -a; // -6 int c = -(-a); cout << a << " " << b << ' ' << c << endl; char A = 'A'; A = A + 1; cout << A << endl; // 结果为B return 0; }
除法的整除问题
取模运算符
两个数相除得到的余数。又叫取余。
#include <iostream> using namespace std; int main() { int a = 100; int b = 9; cout << a % b << endl; // 1 a = 100; b = -9; cout << a % b << endl; // 1 a = -100; b = 9; cout << a % b << endl; // -1 a = -100; b = -9; cout << a % b << endl; // -1 // -1 // 100 = -9 * (-11) + 1 // 1、取模符号和补除数一致 return 0; }
递增递减运算符
#include <iostream> using namespace std; int main() { int i = 6; i++; cout << i << endl; //7 ++i; cout << i << endl; //8 int j = 8; int x = i++; // 先把值给到x,再执行递增 int y = ++j; // 先执行递增,再把值给到y (效率稍微高一点点) cout << x << endl; //8 cout << y << endl; //9 int z = i++ + ++i; // (i++) + (++i) = 9 + 11 = 20 不要这么写 cout << z << endl; i--; ++i; return 0; }
赋值运算符
#include <iostream> using namespace std; int main() { int x = 6; int y = 9; x = y; // 将y的值赋值给x cout << x << endl; // 9 x += y; // x = x + y; cout << x << endl; // 18 x -= y; // x = x - y; cout << x << endl; // 9 x *= y; // x = x * y; cout << x << endl; // 81 x /= y; // x = x / y; cout << x << endl; // 9 x %= y; // x = x % y; cout << x << endl; // 0 return 0; }
比较运算符
#include <iostream> using namespace std; /* == 等于 != 不等于 < 小于 > 大于 <= 小于等于 >= 大于等于 */ int main() { int a = 6; int b = 9; cout << (a == b) << endl; // 0 真用1表示,假用0表示 cout << (a != b) << endl; cout << (a < b) << endl; cout << (a > b) << endl; cout << (a <= b) << endl; cout << (a >= b) << endl; return 0; }
= 和 == 的区别
逻辑运算符
#include <iostream> using namespace std; /* && 与 || 或 ! 非 优先级 || < && < ! */ int main() { // 1. 与运算: 有假必假 cout << (0 && 0) << endl; cout << (0 && 2) << endl; cout << (2 && 0) << endl; cout << (2 && 2) << endl; cout << "----" << endl; // 2. 或运算: 有真必真 cout << (0 || 0) << endl; cout << (0 || 2) << endl; cout << (2 || 0) << endl; cout << (2 || 2) << endl; cout << "----" << endl; // 3. 非运算: 非真即假,非假即真 cout << (!0) << endl; cout << (!2) << endl; cout << "----" << endl; // 复合运算:运算符之间的嵌套 int a = !((5 > 4) && (7 - 8) && (0 - 1)); // !(1 && 1 && 1) 有假必假,但没有假 cout << a << endl; // 0 int b = !(1 || 1 && 0); // 与的优化级比或高 cout << b << endl; // 0 return 0; }
0 0 0 1 ---- 0 1 1 1 ---- 1 0 ---- 0 0
短路原则
逗号运算符
#include <iostream> using namespace std; /* * 逗号表达式优先级很低,比赋值运算符还低 * 逗号表达式是单个分别计算,把最后一个逗号右边的内容作为整个表达式的值 */ int main() { int a = 1; a = 5 - 6, 8 + 9, 100 / 7; // -1, 17, 14 // 上面认为是这样的, (a = 5 - 6), (8 + 9), (100 / 7); cout << a << endl; // -1 a = (5 - 6, 8 + 9, 100 / 7); // -1, 17, 14 cout << a << endl; // 14 // 1. 案例1 int x = 4; int y = 5; int tmp = x; x = y; y = tmp; cout << x << ' ' << y << endl; // 5 4 tmp = x, x = y, y = tmp; cout << x << ' ' << y << endl; // 4 5 return 0; }
逗号的误用
位运算符
位与运算符
#include <iostream> using namespace std; /* & 位与:有0必0;相当于相乘 && 与:有假必假 */ int main() { // 1. 位与运算符的定义 int a = 0b1010; // 二进制1010 8+2=10 int b = 0b0110; // 6 cout << (a & b) << endl; // 2 cout << "----" << endl; // 2. 奇偶性判断 cout << 5 % 2 << endl; // 1 即5模上2余1。% 优先级高、效率高 cout << (5 & 1) << endl; // & 优先级低、效率高 // 5 0b101 // 1 0b001 cout << "----" << endl; // 3. 获取一个数二进制的末5位 int c = 0b1010010101001; // 后5位01001 cout << (c & 0b11111) << endl; cout << "----" << endl; // 4. 将末5位归零 int d = 0b11111111111111111111111111100000; // int 占4字节 cout << (c & d) << endl; cout << "----" << endl; // 5. 消除末尾连续的1 int e = 0b101010111111; // e+1= 0b101011000000; // e&(e+1) = 0b101010000000; cout << (e & (e + 1)) << endl; cout << "----" << endl; // 6. 2的幂判定 int f = 0b1000000; // f-1= 0b0111111; // &= 0 cout << (f & (f - 1)) << endl; // 0 return 0; }
2的幂
位或运算符
#include <iostream> using namespace std; /* | 位或 : 有1即1。相当于相加 || 逻辑或: 有真即真 */ int main() { // 1. 位或的定义 int a = 0b1010; // 10 int b = 0b0110; // 6 // | = 0b1110; // 14 cout << (a | b) << endl; cout << "----" << endl; // 2. 设置标记位 int c = 0b100111; // 0b101111; cout << (c | (0b1000)) << endl; cout << "----" << endl; // 3. 置空标记位 // 0b100111 int d = 0b000001; cout << ((c | d) - d) << endl; cout << "----" << endl; // 4. 低位连续0变成1 int e = 0b1010010000; // e-1= 0b1010001111; // -> 0b1010011111; int f = 0b1010011111; cout << f << endl; cout << (e | (e - 1)) << endl; return 0; }
异或运算符
#include <iostream> using namespace std; /* ^ 异或 如果2个数相同得到值就是0,不相等得到值为1 */ int main() { // 1. 异或的定义 int a = 0b1010; int b = 0b0110; // ^ = 0b1100; cout << (a ^ b) << endl; cout << "----" << endl; // 2. 标记位取反 int c = 0b1000101; // 0b0001000; // 任何数与0异或还是它本身 cout << c << endl; cout << (c ^ 0b0001000) << endl; cout << ((c ^ 0b0001000) ^ 0b0001000) << endl; cout << (((c ^ 0b0001000) ^ 0b0001000) ^ 0b0001000) << endl; cout << "----" << endl; // 3. 变量交换 int d = 17; int e = 19; d = d ^ e; e = d ^ e; // d'^e = d ^ e ^ e 其中e与e异或为0,所以e = d d = d ^ e; // d' ^ e' = d ^ e ^ d = d ^ d ^ e = 0 ^ e,所以 d = e cout << d << ' ' << e << endl; cout << "----" << endl; // 3.1 任何数和0异或,还是它本身 // 3.2 两个相同的数异或,结果为0 // 3.3 异或满足交换律和结合律 // 异或:不带进位的二进制加法 // 4. 出现奇数次的数 // 比如有101个数,有些数出现了偶数次,有些出现了奇数次,并且奇数次的数只有1个,要找出那个数 // 把所有的数进行异或,因为偶数次的数,异或完了都变成0, // 0和剩下的数异或没有任何影响,最终留下的数就是出现了奇数次的数 cout << (42 ^ 42 ^ 3 ^ 2 ^ 2 ^ 6 ^ 6) << endl; cout << (42 ^ 42 ^ 3) << endl; cout << "----" << endl; // 5. 加密 int x = 1314; cout << "520" << x << endl; // 明文 int y = (x ^ 3135); cout << "520" << y << endl; // 加密 cout << "520" << (y^3135) << endl; // 解密 return 0; }
按位取反
只有一个操作数
#include <iostream> using namespace std; /* ~ */ int main() { // 1. 按位取反的定义 int a = 0b00000000000000000000000000000000001; int b = 0b11111111111111111111111111111111110; cout << (~a) << endl; // -2 cout << b << endl; // -2 int c = 0b0; cout << (~c) << endl; // -1 // 2. 求相反数 int d = 18; cout << (~d + 1) << endl; //-18 了解计算机组成原理:原码、反码、补码 // 00000000000000000000000000010010 18 // 11111111111111111111111111101101 ~18 // 11111111111111111111111111101110 ~18+1 在计算机内部,数值一律用补码存储 // 10000000000000000000000000010001 反码 // 10000000000000000000000000010010 补码=反码+1 return 0; }
在计算机系统中,数值一律用补码来存储 ,主要原因是:
- 统一了零的编码
- 将符号位和其它位统一处理
- 将减法运算转变为加法运算
- 两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃
反码 对于正数,反码与原码相同 对于负数,符号位不变,其它部分取反(1变0,0变1) 补码特点: 对于正数,原码、反码、补码相同 对于负数,其补码为它的反码加1 补码符号位不动,其他位求反,最后整个数加1,得到原码 十进制数 原码 +0 0000 0000 -0 1000 0000 十进制数 反码 +0 0000 0000 -0 1111 1111 十进制数 补码 统一了零的编码 +0 0000 0000 -0 10000 0000由于只用8位描述,最高位1丢弃,变为0000 0000
左移运算符
左移一位相当于对原来的这个数乘上2。
#include <iostream> using namespace std; /* x << y 将x左移y位。对二进制数进行左移。其中xy都是整数 x << y = x * 2^y 相当于x乘上2的y次幂 */ int main() { // 1. 正数的左移 int x = 0b11; // 3 x = (x << 1); //0b110 左移1位相当于乘上2 cout << x << endl; // 6 cout << "----" << endl; cout << (x << 4) << endl; // 96 = x * 2^4 = 6 * 16 cout << "----" << endl; // 2. 负数的左移 int y = -1; y = (y << 1); cout << y << endl; // -2 = -1 * 2^1 cout << "----" << endl; // 3. 左移负数位 int z = 64; z = (z << (-1)); // 不能这么写 cout << z << endl; cout << "----" << endl; // 0 // 4. 左移溢出 int a = 64; a = (a << 31); cout << a << endl; // 0 // 64 = 0b1000000 // <<31 = 0b00000000000000000000000000000000 1被截断了 return 0; }
右移运算符
右移一位相当于对原来的这个数除上2。
#include <iostream> using namespace std; /* x >> y 将x右移y位。对二进制数进行左移。其中xy都是整数 x >> y = x / 2^y 相当于x除上2的y次幂 */ int main() { // 1. 正数的右移 int a = 0b111; // 7 a = (a >> 1); // 3=7 / 2^1 cout << a << endl; cout << "----" << endl; // 2. 负数的右移 int b = -1; b = (b >> 1); // 负数在右移的过程中最高位还是补了个1 cout << b << endl; // -1 // 11111111 11111111 11111111 11111111 -1的补码 // 11111111 11111111 11111111 11111111 右移1位=-1 负数在右移的过程中最高位还是补了个1 // 3. 去掉低 k 位 int c = 0b10000101; c = (c >> 7); // 去掉低7位 cout << c << endl; // 1 // 4. 取到第低k位的值 int d = 0b1010101001; cout << ((d >> 7) & 1) << endl; // 1 右移后与1位与 return 0; }
运算符优先级
程序控制流程结构
选择结构
if 语句
#include <iostream> using namespace std; int main() { // 简单语句 int a = 1; int b = a + 1; int c = a + b; cout << a << ' ' << b << ' ' << c << endl; // 复合语句 { int a = 1; int b = a + 1; int c = a + b; cout << a << ' ' << b << ' ' << c << endl; } return 0; }
if else 其实就是复合语句要做的一些事情。
#include <iostream> using namespace std; int main() { int a; cin >> a; // 输入一个值给a //if (a % 2 == 1) { // 这里的 == 1可以去掉,只要条件表达式满足就等于1 if (a % 2) { cout << "a是一个奇数" << endl; } return 0; }
!= 0 和 true 等价
if else 语句
if (表达式) { 语句1; } else { 语句2; }
#include <iostream> using namespace std; int main() { int x; string y = "添砖"; cin >> x; if (x & 1) { cout << "x 是奇数" << endl; y = y + "Java"; } else { cout << "x 不是奇数" << endl; } cout << y << endl; return 0; }
else if 语句
#include <iostream> using namespace std; int main() { if () { } else if () { } else if () { } else { } return 0; }
#include <iostream> using namespace std; int main() { int a; cin >> a; if (a == 0) { cout << "夜深人静写算法" << endl; } else if (a == 1) { cout << "英雄哪里出来" << endl; } else if (a == 2) { cout << "算法大师兄" << endl; } else if (a == 3) { cout << "大师兄编程" << endl; } else { cout << "算法联盟" << endl; } return 0; }
条件运算符
在C++中条件运算符又叫三目运算符。三目其实是说有3个操作数。
#include <iostream> using namespace std; int main() { int a = 3, b = 4; double c = 1.6; int x = -1; if (a > b) { x = a; } else { x = b; } // 表达式1 ? 表达式2 : 表达式3; // 根据表达式1的值,如果是true执行表达式2,false执行表达式3 x = (a > b ? a : b); cout << ((a > b) ? ((a > c) ? a : c) : b) << endl; // 嵌套的条件表达式 return 0; }
注意:
- 它是唯一的一个三目运算符
- 条件运算符是右结合的
- 条件运算符的优先级非常低,仅高于赋值和逗号运算符。优化级把握不住,那就多加括号吧。
switch语句
#include <iostream> using namespace std; int main() { int a = 0; cin >> a; switch (a) { case 0: cout << "Zero" << endl; break; case 1: cout << "One" << endl; // 代码从上往下执行,如果没有break语句则继续向下执行 case 2: cout << "Two" << endl; break; case 3: cout << "Three" << endl; break; default: cout << "Beyond Three or below zero"; } //if (a == 0) { // cout << "Zero" << endl; //} //else if (a == 1) { // cout << "One" << endl; //} //else if (a == 2) { // cout << "Two" << endl; //} //else if (a == 3) { // cout << "Three" << endl; //} //else { // cout << "Beyond Three or below zero"; //} return 0; }
循环结构
重复执行某一段代码
while 语句
#include <iostream> using namespace std; int main() { int count = 0; while (count < 100) { cout << "I love you!" << endl; count += 1; } cout << count << endl; // 死循环 while (1) { cout << "I love you!" << endl; count += 1; } return 0; }
死循环程序,Ctrl+c退出。
范例
#include <iostream> using namespace std; int main() { string a; while (cin >> a) { cout << a + "写算法" << endl; } return 0; }
#include <iostream> using namespace std; int main() { // 输入2个数,计算a+b的合 int a, b; while (cin >> a >> b) { cout << a + b << endl; } return 0; }
do … while 语句
do {
语句;
} while (表达式);
#include <iostream> using namespace std; int main() { /* int a = 0; do { cout << a << endl; a += 1; } while (a<3); a = 0; while (a < 3) { cout << a << endl; a += 1; } */ int a = 3; do { // do 是先执行体,再while cout << a << endl; a += 1; } while (a < 3); cout << "----" << endl; a = 3; while (a < 3) { cout << a << endl; a += 1; } return 0; }
for 语句
for循环能实现while循环的所有功能。
#include <iostream> using namespace std; int main() { //for (初始化; 判断条件; 执行) { // ; //} int sum = 0; for (int i = 1; i <= 5; ++i) { sum += i; } cout << sum << endl; return 0; }
范例
求1到n的和
#include <iostream> using namespace std; int main() { //求1到n的和 int n; while (cin >> n) { /* 1. 写法1 int sum = 0; for (int i = 1; i <= n; ++i) { sum += i; } cout << sum << endl; */ /* 2. 写法2 int sum = 0; int i = 1; for (; i <= n; ++i) { sum += i; } cout << sum << endl; */ /* 3. 写法3 int sum; int i; for (sum = 0, i = 1; i <= n; ++i) { sum += i; } cout << sum << endl; */ /* 4. 写法4 死循环 int sum; int i; for (sum = 0, i = 1; ; ++i) { sum += i; } cout << sum << endl; */ // 5. 写法5 int sum; int i; for (sum = 0, i = 1; i <= n; ) { sum += i; ++i; } cout << sum << endl; // sn =1 + 2 + 3 .. (n-2) + (n-1) + n // sn = n + (n-1) + (n-2) ...4 + 3 + 2 + 1 // 反着排 // 2sn = (n+1) + [(n-1)+2] + [(n-2)+3]..[3+(n-2)]+[2+(n-1)]+(1+n) // 2sn = (n+1) + (n+1)+ (n+1)..(n+1) + (n+1)+ (n+1) // sn = n(n+1)/2 cout << (n * (n + 1) / 2) << endl; } return 0; }
变量作用域问题
跳转结构
break
跳出本次循环。
#include <iostream> using namespace std; int main() { // 1. while中的break; int cnt = 0; while (1) { cnt++; cout << "当前次数为:" << cnt << endl; if (cnt > 20) { break; } } cout << "----------" << endl; // 2. for 中的break; cnt = 20; for (int i = 1; ; ++i) { cout << "当前次数为:" << i << endl; if (i > cnt) { break; } } // 3. switch case中的break; int a = 1; switch (a) { case 1: cout << "算法联盟" << endl; break; case 2: cout << "夜深人静写算法" << endl; break; } return 0; }
continue
continue在循环内部结束掉本次循环,进行下一次循环。
#include <iostream> using namespace std; int main() { for (int i = 0; i < 10; ++i) { if (i < 3) { continue; } cout << i << endl; } int a; while (cin >> a) { if (a == 0) { continue; } cout << a << endl; } return 0; }
练习
A+B
/* HDOJ 1000 A + B Problem https://acm.hdu.edu.cn/showproblem.php?pid=1000 */ #include <iostream> using namespace std; int main() { int a, b; while (cin >> a >> b) { cout << a + b << endl; } return 0; }
/* HDOJ 1089 A+B for Input-Output Practice (I) https://acm.hdu.edu.cn/showproblem.php?pid=1089 */ #include <iostream> using namespace std; int main() { int a, b; while (cin >> a >> b) { cout << a + b << endl; } return 0; }
/* HDOJ 1091 A+B for Input-Output Practice (III) https://acm.hdu.edu.cn/showproblem.php?pid=1091 输入:反复输入 a 和 b,当 a 和 b 都等于 0 的时候,程序结束; 输出:a 和 b 的和 */ #include <iostream> using namespace std; int main() { int a, b; while (cin >> a >> b) { if (!a && !b) { break; } cout << a + b << endl; } return 0; }
/* HDOJ 1092 A+B for Input-Output Practice (IV) https://acm.hdu.edu.cn/showproblem.php?pid=1092 输入:输入一个 n,然后 n 个数;当 n 等于 0 的时候,程序结束; 输出:输出这 n 个数的和; */ #include <iostream> using namespace std; int main() { int n, x, sum; while (cin >> n && n) { sum = 0; for (int i = 0; i < n; ++i) { cin >> x; sum += x; } cout << sum << endl; } return 0; }
/* HDOJ 1093 A+B for Input-Output Practice (V) https://acm.hdu.edu.cn/showproblem.php?pid=1093 输入:输入一个t,代表有t组数据,每组数据输入一个 n,然后n个数; 输出:对于t组数据,输出这n个数的和 */ #include <iostream> using namespace std; int main() { int t, n, x, sum; cin >> t; while(t--) { sum = 0; cin >> n; for (int i = 0; i < n; ++i) { cin >> x; sum += x; } cout << sum << endl; } return 0; }
/* HDOJ 1094 A+B for Input-Output Practice (VI) https://acm.hdu.edu.cn/showproblem.php?pid=1094 输入:输入一个n,然后n个整数 输出:n行,每行一个整数,代表所有这一行输入的元素之和 */ #include <iostream> using namespace std; int main() { int n, x, sum; while (cin >> n) { sum = 0; for (int i = 0; i < n; ++i) { cin >> x; sum += x; } cout << sum << endl; } return 0; }
/* HDOJ 1095 A+B for Input-Output Practice (VII) https://acm.hdu.edu.cn/showproblem.php?pid=1095 输入:反复输入 a 和 b 输出:输出 a 和 b 的和,并且带上一个换行 */ #include <iostream> using namespace std; int main() { int a, b; while (cin >> a >> b) { cout << a + b << endl; cout << endl; } return 0; }
/* HDOJ 1096 A+B for Input-Output Practice (VIII) https://acm.hdu.edu.cn/showproblem.php?pid=1096 输入:输入一个t,代表有t组数据;每组数据输入一个n,输入n个数; 输出:输出 t 个数,代表每组数据的元素和,且 t 个数之间加上一个换行; */ #include <iostream> using namespace std; int main() { int t, n, x, sum; cin >> t; while (t--) { sum = 0; cin >> n; for (int i = 0; i < n; ++i) { cin >> x; sum += x; } cout << sum << endl; if (t) { cout << endl; } } return 0; }
/* HDOJ 2096 小明A+B https://acm.hdu.edu.cn/showproblem.php?pid=2096 输入:先输入一个t,然后t组数据,每组数据输入a和b; 输出:输出 t 行,每一行代表 a 和 b 的和,并且模上100; */ #include <iostream> using namespace std; int main() { int t, a, b; cin >> t; while (t--) { cin >> a >> b; int c = (a % 100 + b % 100) % 100; cout << c << endl; } return 0; }
/* HDOJ 2033 人见人爱A+B https://acm.hdu.edu.cn/showproblem.php?pid=2033 输入:输入一个t,代表t组数据,每组数据输入两个时间,时间分别为时分秒 输出:输出t行,每一行是一个时间,代表这组数据下两个时间的加和,注意考虑进位 */ #include <iostream> using namespace std; int main() { int t; int ah, am, as; int bh, bm, bs; cin >> t; while (t--) { cin >> ah >> am >> as; cin >> bh >> bm >> bs; as += bs; am += bm; ah += bh; am += as / 60; as %= 60; ah += am / 60; am %= 60; cout << ah << ' ' << am << ' ' << as << endl; } return 0; }
A|B?
A|B?
/* HDOJ 2075 A|B? https://acm.hdu.edu.cn/showproblem.php?pid=2075 输入:输入一个t,代表t组数据,每组数据输入两个数 a 和 b 输出:对于每组数据,如果 a模b 等于 0,输出 YES,否则输出 NO */ #include <iostream> using namespace std; int main() { int t, a, b; cin >> t; while (t--) { cin >> a >> b; cout << (a % b == 0 ? "YES" : "NO") << endl; } return 0; }
An easy problem
/* HDOJ 2055 An easy problem https://acm.hdu.edu.cn/showproblem.php?pid=2055 定义 f(A) = 1, f(a) = -1, f(B) = 2, f(b) = -2, ... f(Z) = 26, f(z) = -26; 输入:输入一个t,代表t组数据,每组数据输入字符 x 和整数 y 输出:对于每组数据,输出 y+f(x) 的值 */ #include <iostream> using namespace std; int main() { int t; cin >> t; while (t--) { char s[3]; // 字符用字符数组来接收 char x; int y; int ans = 0; cin >> s; x = s[0]; cin >> y; // y + f(x) if (x >= 'A' && x <= 'Z') { ans = y + (x - 'A' + 1); } else if (x >= 'a' && x <= 'z') { ans = y - (x - 'a' + 1); } cout << ans << endl; } return 0; }
成绩转换
/* HDOJ 2004 成绩转换 https://acm.hdu.edu.cn/showproblem.php?pid=2004 输入一个百分制的成绩t,将其转换成对应的等级,具体转换规则如下: 90~100为A; 80~89为B; 70~79为C; 60~69为D; 0~59为E; 输入:反复输入一个整数n 输出:根据规则输出字符,如果不在0-100范围内,输出 Score is error! */ #include <iostream> using namespace std; int main() { int x; while (cin >> x) { char ret = '\0'; if (x >= 90 && x <= 100) { ret = 'A'; } else if (x >= 80 && x <= 89) { ret = 'B'; } else if (x >= 70 && x <= 79) { ret = 'C'; } else if (x >= 60 && x <= 69) { ret = 'D'; } else if (x >= 0 && x <= 59) { ret = 'E'; } if (ret == '\0') { cout << "Score is error!" << endl; } else { cout << ret << endl; } } return 0; }
Picture
/* HDOJ 2052 Picture https://acm.hdu.edu.cn/showproblem.php?pid=2052 输入:反复输入 n 和 m 输出:输出一个 m x n 的矩形 3 2 +---+ | | | | +---+ */ #include <iostream> using namespace std; int main() { int n, m; while (cin >> n >> m) { // 1、第一行 cout << '+'; for (int i = 0; i < n; ++i) { cout << '-'; } cout << '+' << endl; // 2、中间的行 for (int i = 0; i < m; ++i) { cout << '|'; for (int j = 0; j < n; ++j) { cout << ' '; } cout << '|' << endl; } // 3、最后一行 cout << '+'; for (int i = 0; i < n; ++i) { cout << '-'; } cout << '+' << endl; cout << endl; } return 0; }
Max Num
/* HDOJ 2071 Max Num https://acm.hdu.edu.cn/showproblem.php?pid=2071 输入:输入一个t,代表t组数据,每组数据输入一个n,以及n个浮点数 输出:对于每组数据,输出这n个数中的最大值 */ #include <iostream> using namespace std; int main() { int t; cin >> t; while (t--) { int n; double x; double max = 0; cin >> n; for (int i = 0; i < n; ++i) { cin >> x; if (x > max) { max = x; } } printf("%.2lf\n", max); } return 0; }
数组
在程序设计时,有时候为了处理方便,我们会把一些相同类型的变量有序的组织在一起。这些组织在一直的数据元素叫数组。
在C++中,数组属于构造数据类型,就按数据的类型不同可以分为:
- 数值数组
- 字符数组
- 指针数组
- 结构体数组等等
一维数组
#include <iostream> using namespace std; int main() { // 1. 数组的定义 // 数据类型 数组名[数字/常量表达式]; // 数字代表有多少个元素 int a[1024] = { 1,2,3,45 }; // 具有1024个元素的整型数组 double b[520]; char c[1314]; // 具有1314个元素的字符数组 // double a[1024]; 错误,不能用相同变量 // a[0] a[1] a[2] //int n = 1000; //int d[n]; 错误,方括号内必须是常量 int x1[10], x2[20], x3[30]; // 注意 // 1. 数组内的所有元素类型都是相同的 // 2. 数组名其实就是变量名,不能和其它变量同名,并且要满足标识符的那些规则 // 3. 数组下标是从0开始。 // 4. 方括号里是个变量,是不合理的。必须是常量,不是常量就不知道怎么分配内存。 // 5. 数组定义可以写在一行上 // 2. 数组元素的访问 // 数组名[下标] a[0], a[1023]; for (int i = 0; i < 4; ++i) { cout << a[i] << endl; } // 3. 逆序输出 // 输入一个n(n<100),和n个数,请逆序输出这n个数 int n; int x[100]; cin >> n; for (int i = 0; i < n; ++i) { cin >> x[i]; } for (int i = n - 1; i >= 0; --i) { cout << x[i] << ' '; } cout << endl; return 0; }
变长定义
二维数组
一维数组可以理解为数据结构中的顺序表,只不过在C++里面的数组是一个静态数组,它是不需要做扩容的,也就是说必须提前知道数组元素的个数,如果超过了会造成数组下标越界。
二维数组实际上是一种特殊的数组,它可以用来存储表格,具有行和列的那种数据结构。每个二维数组其实是由若干个一维数组组成的。
二维数组一般可以和线性代数中的矩阵联系起来。
#include <iostream> using namespace std; int main() { // 1. 二维数组的定义 // 数组类型 数组名[行][列] // 数组名就是这个数组的变量名;行代表有多少行;列就是每一行上有多少个元素 // 定义一个3行4列的二维数组 int arr[3][4]; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { arr[i][j] = i * j; } } // 初始化部分元素时,未初始化的部分默认为0 int b[4][4] = { {1, 2}, {2, 3}, {6, 7 , 8}, {9, 1} }; for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { cout << b[i][j] << ' '; } cout << endl; } // 数组名其实代表了这个数组元素的首地址 return 0; }
练习
求奇数的乘积
/* HDOJ 2006 求奇数的乘积 https://acm.hdu.edu.cn/showproblem.php?pid=2006 输入:反复输入n,并且跟上n个数字; 输出:对于每组输入的数据,输出所有奇数的乘积 */ #include <iostream> using namespace std; int a[1000000]; int main() { int n; while (cin >> n) { for (int i = 0; i < n; ++i) { cin >> a[i]; } int prod = 1; for (int i = 0; i < n; ++i) { if (a[i] % 2) { prod *= a[i]; } } cout << prod << endl; } return 0; }
平方和与立方和
/* HDOJ 2007 平方和与立方和 https://acm.hdu.edu.cn/showproblem.php?pid=2007 输入:反复输入 n 和 m; 输出:输出 n 到 m 中的所有偶数的平方和,以及所有奇数的立方和 */ #include <iostream> using namespace std; int a[1000000]; int main() { int n, m; while (cin >> n >> m) { if (n > m) { int tmp = n; n = m; m = tmp; } int cnt = m - n + 1; for (int i = n; i <= m; ++i) { a[i - n] = i; } int sum1 = 0, sum2 = 0; for (int i = 0; i < cnt; ++i) { if (a[i] % 2 == 0) { sum1 += a[i] * a[i]; } else { sum2 += a[i] * a[i] * a[i]; } } cout << sum1 << ' ' << sum2 << endl; } return 0; }
海选女主角
/* HDOJ 2022 海选女主角 https://acm.hdu.edu.cn/showproblem.php?pid=2022 给定一个 m x n 的矩阵 找出矩阵中绝对值最大的数 如果数值相等,则返回行号最小的,如果行号相等,则返回列号最小的 输入:反复输入 m 和 n,以及 m x n 的矩阵。 输出:行号、列号 以及 绝对值最大的那个数 */ #include <iostream> #include <cmath> using namespace std; int a[1001][1001]; int main() { int m, n; while (cin >> m >> n) { int max = -1, r, c; for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { cin >> a[i][j]; } } for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { int x = abs(a[i][j]); if (x > max) { max = x; r = i; c = j; } } } cout << (r + 1) << ' ' << (c + 1) << ' ' << a[r][c] << endl; } return 0; }
为什么定义数组放int a[1001][1001];在int main()外面才行呢?
- 函数内部的变量是存储在栈上的,如果太大,容易产生栈溢出。放在 int main 外部,就是全局区的内存。可以去看下 内存管理 那个章节
- a[1001][1001] 的数据太大了,不能放在栈上,只能放在堆或者全局区。解决:
- 利用 new 来申请内存空间,把内存放到堆上。
- 更简单的,把数组变量定义成全局数组变量。
求平均成绩
/* HDOJ 2023 求平均成绩 https://acm.hdu.edu.cn/showproblem.php?pid=2023 假设一个班有n(n<=50)个学生,每人考m(m<=5)门课,求每个学生的平均成绩和每门课的平均成绩,并输出各科成绩均大于等于平均成绩的学生数量。 输入:反复输入 n 和 m,分别表示学生数和课程数。然后是n行数据,每行包括m个整数。 输出:对于每个测试实例,输出3行数据: 第一行包含n个数据,表示n个学生的平均成绩,结果保留两位小数; 第二行包含m个数据,表示m门课的平均成绩,结果保留两位小数; 第三行是一个整数,表示该班级中各科成绩均大于等于平均成绩的学生数量。 每个测试实例后面跟一个空行。 3 2 1 2 3 4 6 8 1.50 3.50 7.00 3.33 4.67 1 7 5 1 2 3 4 5 5 4 3 2 1 6 5 4 3 2 2 3 4 5 6 9 9 9 9 9 8 8 8 8 8 7 7 7 7 7 */ #include <iostream> using namespace std; double a[51][6]; double sa[51]; double ca[6]; int main() { int n, m; while (cin >> n >> m) { for (int i = 0; i < n; ++i) { for (int j = 0; j < m; ++j) { cin >> a[i][j]; } } for (int i = 0; i < n; ++i) { sa[i] = 0; for (int j = 0; j < m; ++j) { sa[i] += a[i][j]; } sa[i] /= m; } for (int i = 0; i < m; ++i) { ca[i] = 0; for (int j = 0; j < n; ++j) { ca[i] += a[j][i]; } ca[i] /= n; } int cnt = 0; for (int i = 0; i < n; ++i) { int sum = 0; for (int j = 0; j < m; ++j) { sum += (a[i][j] >= ca[j]); } if (sum == m) { ++cnt; } } for (int i = 0; i < n; ++i) { if (i) { cout << ' '; } printf("%.2lf", sa[i]); } cout << endl; for (int i = 0; i < m; ++i) { if (i) { cout << ' '; } printf("%.2lf", ca[i]); } cout << endl; cout << cnt << endl; } return 0; }
函数
函数的定义
function函数是把一些经常用到的代码给它封装起来,这样就可以减少一些冗余代码。
#include <iostream> using namespace std; /* 1. 返回值类型:可以是整型、浮点型、字符串类型、字符类型等 2. 函数名 3. 参数列表:圆括号加上逗号分隔的表达式组成 4. 函数体 5. return 表达式(返回值) 返回值类型 函数名(参数列表) { 函数体 return 表达式(返回值); } */ int add(int a, int b) { int c = a + b; return c; } void printAaddB(int a, int b) // void 返回类型为空的函数 { int c = a + b; cout << c << endl; } int max1(int a, int b) { return a > b ? a : b; } int main() { int ret = add(1, 7); // 函数的调用 cout << ret << endl; return 0; }
函数的调用
#include <iostream> using namespace std; /* 1. 返回值类型:可以是整型、浮点型、字符串类型、字符类型等 2. 函数名 3. 参数列表:圆括号加上逗号分隔的表达式组成 4. 函数体 5. return 表达式(返回值) 返回值类型 函数名(参数列表) { 函数体 return 表达式(返回值); } */ int add(int a, int b) { return a + b; } int max(int a, int b) { return a > b ? a : b; } int sum(int n) { /* 枚举算法 int ret = 0; for (int i = 0; i < n; ++i) { ret += i; } return ret; */ return (1 + n) * n / 2; // 公式 } int main() { // 1. 加法的调用 int a = add(1, 7); // 函数的调用 int b = add(a, 9); cout << b << endl; // 2. 最大值的调用 int c = max(a, b); // 3. 求1到n的和 int n; cin >> n; int d = sum(n); cout << d << endl; return 0; }
函数值传递
把一些参数传到函数体内部,然后让函数体内部去使用它。
#include <iostream> using namespace std; /* | a | b | a | b | tmp | | 6 | 9 | 6 | 9 | 6 | | a | b | a | b | tmp | | 6 | 9 | 9 | 6 | 6 | 函数执行完毕后,函数内部变量会被销毁掉,a, b, tmp | a | b | | 6 | 9 | */ void swap(int a, int b) { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "----" << endl; int tmp = a; a = b; b = tmp; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "----" << endl; } int main() { int a = 6; int b = 9; swap(a, b); // 值传递。传进去的数只是拷贝了一份,并不是原来的那个数据 // 虽然函数里进行了交换,但外部无法进行交换 // 学完指针就知道怎么进行交换了 cout << "a = " << a << endl; cout << "b = " << b << endl; return 0; }
函数的声明
#include <iostream> using namespace std; // C++程序从上往下执行 int add(int a, int b) { return a + b; } int main() { int x, y; cin >> x >> y; int z = add(x, y); cout << z << endl; return 0; }
#include <iostream> using namespace std; // C++程序从上往下执行 // 把函数写在main的后面就需要声明它 int add(int a, int b); int add(int, int); // 只要把类型定义好就可以,声明多次也是可以的 // 场景 void func2(int); // func1需要提前调用func2时,需要提前声明 void func1(int x) { if (x <= 0) { return; } cout << "func1: " << x << endl; func2(x-1); } void func2(int x) { cout << "func2: " << x << endl; func1(x-1); } int main() { int x, y; cin >> x >> y; int z = add(x, y); cout << z << endl; func1(10); return 0; } int add(int a, int b) { return a + b; }
非安全函数
scanf和strcpy 是一个不安全函数,所以加上 _s 就是 safe 也就是安全版本
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; int main() { int a; //scanf("%d", &a); // 报错 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details. //scanf_s("%d", &a); scanf("%d", &a); // 使用#define _CRT_SECURE_NO_WARNINGS //C++中还有个函数strcpy char str[10]; //strcpy(str, "hahahaha"); // 报错, 与scanf相同 //strcpy_s(str, "hahahaha"); strcpy(str, "hahahaha"); // 使用#define _CRT_SECURE_NO_WARNINGS return 0; }
递归
递归的理解
练习
求绝对值
/* HDOJ 2003 求绝对值 https://acm.hdu.edu.cn/showproblem.php?pid=2003 输入:反复输入一个实数x 输出:输出 x 的绝对值,保留两位小数 */ #include <iostream> #include <cmath> using namespace std; int main() { double x; while (cin >> x) { x = fabs(x); printf("%.2lf\n", x); } return 0; }
计算两点间的距离
/* HDOJ 2001 计算两点间的距离 https://acm.hdu.edu.cn/showproblem.php?pid=2001 输入:输入两个坐标(x1, y1) 和 (x2, y2) 输出:输出两个坐标的欧几里得距离,并且保留两位有效数字。 */ #include <iostream> #include <cmath> using namespace std; int main() { double x1, y1, x2, y2; while (cin >> x1 >> y1 >> x2 >> y2) { double d = (x1 - x2)* (x1 - x2) + (y1 - y2) * (y1 - y2); printf("%.2lf\n", sqrt(d)); // sqrt开根号 } return 0; }
ASCII码排序
/* HDOJ 2000 ASCII码排序 https://acm.hdu.edu.cn/showproblem.php?pid=2000 输入:一个三个字符的字符串 输出:输出三个字符排序后的结果,并用空格进行分割 */ #include <iostream> #include <algorithm> #include <string> using namespace std; int main() { string s; while (cin >> s) { sort(s.begin(), s.end()); cout << s[0] << ' ' << s[1] << ' ' << s[2] << endl; } return 0; }
三角形
/* HDOJ 2039 三角形 https://acm.hdu.edu.cn/showproblem.php?pid=2039 输入:输入 t 组数据,每组数据是三个数 a b c 输出:对于每组数据,如果三个数能够组成三角形,就输出 YES;否则输出 NO */ #include <iostream> #include <algorithm> using namespace std; double a[3]; int main() { int t; cin >> t; while (t--) { cin >> a[0] >> a[1] >> a[2]; sort(a, a + 3); // a数组进行排序,从第0个元素到第2个元素排序。 a代表a[0]地址,a+3代表a[2]这个地址的后面一个位置 if (a[0] + a[1] > a[2]) { cout << "YES" << endl; } else { cout << "NO" << endl; } } return 0; }
素数判定
素数:不能分解成两个非1的数的乘积
/* HDOJ 2012 素数判定 https://acm.hdu.edu.cn/showproblem.php?pid=2012 对于表达式n^2+n+41 当n在(x,y)范围内取整数值时(包括x,y)(-39<=x<y<=50) 判定该表达式的值是否都为素数 输入:反复输入 x 和 y,当 x 和 y 都为 0 的时候,程序结束 输出:如果满足都是素数,则输出 OK,否则输出 Sorry */ #include <iostream> using namespace std; // // x 有一个因子叫 i,那么必然有另一个因子叫 x/i // i 和 x/i 必然有个大小关系,无论大小关系怎样,都能推导出 i <= 根号x bool isPrime(int x) { for (int i = 2; i*i <= x; ++i) { if (x % i == 0) { return false; } } return true; } int main() { int x, y; while (cin >> x >> y) { if (!x && !y) { break; } bool flag = false; for (int i = x; i <= y; ++i) { int z = i * i + i + 41; if (!isPrime(z)) { flag = true; break; } } if (flag == false) { cout << "OK" << endl; } else { cout << "Sorry" << endl; } } return 0; }
发工资咯
/* HDOJ 2021 发工资咯:) https://acm.hdu.edu.cn/showproblem.php?pid=2021 如果每个老师的工资额都知道,最少需要准备多少张人民币,才能在给每位老师发工资的时候都不用老师找零呢? 这里假设老师的工资都是正整数,单位元,人民币一共有100元、50元、10元、5元、2元和1元六种。 输入:反复输入一个整数n(n<100),表示老师的人数,然后是n个老师的工资。n=0表示输入的结束,不做处理。 输出:输出一个整数 x, 表示至少需要准备的人民币张数。 */ #include <iostream> using namespace std; // 753 = 7 + 1 + 1 + 1 = 10 int calc(int n) { int b[] = { 100, 50, 10, 5, 2, 1 }; int cnt = 0; for (int i = 0; i < 6; ++i) { int x = n / b[i]; cnt += x; n -= x * b[i]; } return cnt; } int main() { int n; int x; while (cin >> n && n) { int sum = 0; for (int i = 0; i < n; ++i) { cin >> x; sum += calc(x); } cout << sum << endl; } return 0; }
指针
指针和地址
/* ________________________________________________________________________________________ 0X4B32F824|0X4B32F825|0X4B32F826|...|...|...|...|...| 内存地址 11 9 变量值 a b 变量名 ---------------------------------------------------------------------------------------- char a = 10; char b = 5; a = 11; b = 9; ____________________________________________ 0x00000010|0x00000011|0x00000012|0x00000013| 内存地址 0X4B32F824 变量值 pa 变量名 */ #include <iostream> using namespace std; int main() { char a = 10; // cout << (&a) << endl; // &a表示a的地址 // printf( "%#X", &a ); // 0X4B32F824 格式字符串不匹配:%#X 用于格式化无符号整数,但 &a 是 char* 类型 printf("%p", (void*)&a); // 使用 %p 格式化指针,并转换为 void* return 0; }
地址就是指针。C语言中,用变量存储数据,用函数来定义一段代码,最终其实都是放内存中,CPU只能通过地址取内存中的数据和代码来用。程序在执行过程中会告诉CPU要执行的代码地址数据。
指针的定义和使用
学习一下在C++中,指针变量是如何定义和使用的。
指针变量其实就是变量,它也是一种变量类型。
数据在内存中的地址,我们称它为指针。如果一个变量存储了一份数据的指针,那么这个变量,我们就叫它指针变量。
/* ________________________________________________________________________________________ 0X00000004|0X00000008|...|...|...|...|...|...| 内存地址 10 20 变量值 a b 变量名 ---------------------------------------------------------------------------------------- ____________________________________________ 0x00000010|0x00000011|0x00000012|0x00000013 内存地址 0X00000004 变量值 pa 变量名 -------------------------------------------------- */ #include <iostream> using namespace std; int main() { int a = 10; int b = 20; // 1. 指针变量的定义 // 数据类型 * 指针变量名; int* pa; // int * pa; int *pa; // *号位置跟int、pa或中间都可以 pa = &a; // 指针变量可以多次赋值 //pa = &b; printf("%#X %#X\n", &a, pa); // pa存储的就是a的地址 // 2. 指针的解引用 // *指针变量名 = 数值; *pa = 7; // *pa 把pa地址里面的值取出来,并且把位置设置成了7。pa指向的地址其实就是a cout << a << ' ' << (*pa) << endl; // 7 7 cout << "-----" << endl; // * 和 取地址& 的关系 // 理解成 * 和 & 是互逆的关系,一个是加号一个是减号 //*&a 等价于 *(&a) = *pa = a //&*pa = &(*pa) = &a = pa; cout << (*&a) << endl; cout << (*(&a)) << endl; cout << (*pa) << endl; cout << (a) << endl; cout << "-----" << endl; cout << (&*pa) << endl; cout << (&(*pa)) << endl; cout << (&a) << endl; cout << (pa) << endl; // *号作用:乘法、定义指针、解引用 return 0; }
指针的内存空间
#include <iostream> using namespace std; int main() { cout << sizeof(int*) << endl; cout << sizeof(short*) << endl; cout << sizeof(char*) << endl; cout << sizeof(long*) << endl; cout << sizeof(long long*) << endl; cout << sizeof(double*) << endl; cout << sizeof(float*) << endl; // 结果都是8。 // 因为所有的指针类型大小是一样的,只跟操作系统有关。 // 32操作系统,字节数为4;64操作系统,字节数为8; return 0; }
空指针和野指针
#include <iostream> using namespace std; int main() { // 1. 空指针 int* p = NULL; // 如果是NULL的情况下,解引用会报错,因为拿不到它的值 //cout << (*p) << endl; // 2. 野指针 p = (int *)0x121412; // 如果是野指针的情况下,解引用会报错,因为不知道 cout << (*p) << endl; return 0; }
void *
int main() { datas ds; cout << &ds.i << "," << &ds.d << "," << (void*)ds.s << endl; }
void * 代表的就是一个空指针,你要输出一个地址的时候,如果打印不出正确的地址,那就在你要打印的地方,写上一个 (void *),代表把你要打印的内容,转换成一个指针变量,打印出来~
const 修饰符
指针常量
#include <iostream> using namespace std; /* const 和 指针的关系 */ int main() { int a = 1; int b = 2; // 1. 指针常量 // 指针的值是一个常量,即指针所指的地址不可改变 int* const p = &a; //p = &b; 错误 *p = 6; cout << "a = " << a << endl; // 6 return 0; }
常量指针
#include <iostream> using namespace std; int main() { int a = 1; int b = 2; // 常量指针 // 指向常量的指针。即指针所指向的地址中的值,不能通过这个指针来改变。必须是可改的左值。 const int* p = &a; // 常量放前面,指针放后面 //*p = 6; // 报错 p = &b; cout << "p = " << *p << endl; // 2 return 0; }
常量指针常量
#include <iostream> using namespace std; int main() { int a = 1; int b = 2; // 常量指针常量 // 即是指针常量又是常量指针,所以本身的值和指向地址中的值都不能改变。 const int* const p = &a; //*p = 6; // 报错 //p = &b; // 报错 return 0; }
| 名称 | 格式 | 说明 |
|---|---|---|
| 指针常量 | type* const | 指针值是一个常量;指针无法被赋值 |
| 常量指针 | const type* | 指向常量的指针;指针解引用后无法被赋值 |
| 常量指针常量 | const type* const | 指针值和指针指向的值都常量;指针和解引用都无法被赋值 |
指针和数组
指针和数组的关系
#include <iostream> using namespace std; /* ------------------------------- | 5 | 4 | 3 | 2 | 1 | ------------------------------- 4 4 4 4 4 */ int main() { // 1、利用指针来访问数组元素 int a[5] = { 5,4,3,2,1 }; cout << "数组第一个元素:" << a[0] << endl; int* p = a; // a 代表数组的首地址 cout << "数组元素首地址:" << a << endl; cout << "指针指向的地址:" << p << endl; cout << "指针访问:数组的第一个元素" << *p << endl; // 5 解引用 cout << "数组第二个元素的地址:" << &a[1] << endl; cout << "指针 + 1 指向的地址:" << p + 1 << endl; // 与&a[1]相同 cout << "指针访问数组第二个元素:" << *(p + 1) << endl; cout << "数组第二个元素的地址:" << &a[2] << endl; cout << "指针 + 2 指向的地址:" << p + 2 << endl; cout << "指针访问数组第三个元素:" << *(p + 2) << endl; p++; // p 地址发生变化。它知道整型int的大小是4个字节 cout << "指针访问:数组第二个元素: " << *p << endl; // a++; p = a; // 把数组的首地址赋给指针 for (int i = 0; i < 5; ++i) { cout << "数组的第" << (i + 1) << "个元素是" << *p << endl; p++; } double b[4] = { 4,3,2,1 }; double* pd = b; cout << "第一个元素的地址:" << pd << endl; // 000000961EEFFC28 pd++; cout << "第二个元素的地址:" << pd << endl; // 000000961EEFFC30 // 28 --> 30 偏移了8个字节:29 2A 2B 2C 2D 2E 2F 30 // 指针的+1,实际上是偏移了 sizeof(对应的指针类型) 个字节 // char、short、long long、float return 0; }
指针数组
#include <iostream> using namespace std; int main() { char a[] = "I"; char b[] = "love"; char c[] = "you"; // 指针数组 char* p[3]; p[0] = a; p[1] = b; p[2] = c; for (int i = 0; i < 3; ++i) { cout << p[i] << ' '; } cout << endl; // 可以用来存储二维数组或矩阵 int mat[3][4]{ {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }; int* pmat[3]; pmat[0] = mat[0]; // mat[0]的首地址给pmat[0] pmat[1] = mat[1]; pmat[2] = mat[2]; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { cout << *(pmat[i] + j) << ' '; } cout << endl; } return 0; }
数组指针
类似二维数组。可以用数组指针操作二维数组上的数据。
#include <iostream> using namespace std; int main() { int(*p)[5]; int a[4][5] = { {4,3,2,1,0}, {9,8,7,6,5}, {6,7,8,9,0}, {5,6,7,8,9} }; p = a; cout << p << endl; // E0 -> EF -> F4 cout << p + 1 << endl; // F4 // 20 个字节 // 20 = 4 * 5 = sizeof(int) * 5 cout << p << ':' << &a[0] << endl; // 完全相等 cout << p + 1 << ':' << &a[1] << endl; return 0; }
指针数组和数组指针
前置知识
int a[1024]; // 长度为1024的整型数组; int* p = a; // a是数组的首地址, p是指向数组第0个元素的指针 a = &(a[0]); // 对a[0]取地址就是a; 也就是p; p和p+0是一样的 p = &(a[0]); p + 0 = &(a[0]) p + i = &(a[i]) *(p+i) = *(&a[i]) = a[i] // 解引用 a 又等于p *(p+i) = p[i]
二维数组-值a[i][j]
| 1 | 2 | 3 | 4 |
| 5 | 4 | 6 | 7 |
| 9 | 10 | 11 | 12 |
二维数组的地址 &(a[i][j])
- 二维数组上的每个元素都有存储它的地址,并且按照行优先的方式进行存储
- 每一行相邻俩元素差值是4,如18和1C相差4个字节。
- 每一列差值是16,如F18和F28
| F18 | F1C | F20 | F24 |
| F28 | F2C | F30 | F34 |
| F38 | F3C | F40 | F44 |
指针数组
int *p[3] // 3代表行数 如果定义一个长度为3的指针数组,并且把里面的每个元素赋值为这个二维数组每一行的第一个元素的首地址。F18 F28 F38 指针数组p的值p[i] p[1] --> F18 = &a[1][0] = a[1] p[2] --> F28 = &a[2][0] = a[2] p[i] == &(a[i][0]) == a[i] 指针数组p的地址 &(p[i]) A38 A40 A48 &(p[i]) = p + i 指针数组地址 没啥用 &(p[0]) = p + 0 = A38 &(p[1]) = p + 1 = A40 指针数组p的值 p[1] + 0 p[1] + 0 二维数组第2行的第1个元素的地址,解引用 *(p[1] + 0) 得到5 p[1] + 1 二维数组第2行的第2个元素的地址,解引用 *(p[1] + 1) 得到4 p[1] + 2 二维数组第2行的第3个元素的地址,解引用 *(p[1] + 2) 得到6 p[1] + 3 二维数组第2行的第4个元素的地址,解引用 *(p[1] + 3) 得到7
数组指针
int (*p)[4]; // 4代表列数 值a[i][j] 地址&(a[i][j]) 数组指针p的值 p --> F18 整个数组 - 数组指针是个值,不是数组 - 数组指针是指向一整个数组的,不是指向数组值 - 数组指针是二级指针,是地址的地址。 p + 0 --> F18 整个数组 p + 1 --> F28 整个数组 p + 2 --> F38 整个数组 *(p+0) a数组的第0行第0个元素的地址 *(p+2) a数组的第2行第0个元素的地址 *(p+2) +1 a数组的第2行第1个元素的地址
#include <iostream> using namespace std; string getHex(int x) { char buff[10]; sprintf_s(buff, "%X", (x & 0xFFFF)); // 取16进制的最后4位 return (string)buff; } int main() { int a[3][4]{ {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { if (j) { cout << ","; } int* p = &a[i][j]; //cout << (void*)p ; // 转成整数(int*)p比较难看,这里不转了(void*)p cout << getHex((int)p); } cout << endl; } // 指针数组 //int* q[3] = { a[0], a[1], a[2] }; int* q[3] = { &a[0][0], &a[1][0], &a[2][0]}; // 数组指针 int (*p)[4]; // 指向的长度为4的数组 p = &a[0]; cout << "1. 指针 + i" << endl; // q + i \ p + i cout << "数组指针" << endl; for (int i = 0; i < 3; ++i) { string s = getHex((int)(p + i)); cout << "第" << i << "个[4]数组的地址是" << s << endl; } cout << "指针数组(没啥用)" << endl; for (int i = 0; i < 3; ++i) { string s = getHex((int)(q + i)); cout << "第" << i << "个q元素的地址是" << s << endl; } cout << "2. *(指针 + i)" << endl; // *(q + i) \ *(p + i) cout << "数组指针" << endl; for (int i = 0; i < 3; ++i) { string s = getHex((int)*(p + i)); cout << "a数组的第" << i << "行第0个元素的地址是" << s << endl; } cout << "指针数组" << endl; for (int i = 0; i < 3; ++i) { string s = getHex((int)*(q + i)); cout << "a数组的第" << i << "行第0个元素的地址是" << s << endl; } cout << "3. *(指针 + i) + j" << endl; cout << "数组指针" << endl; for (int i = 0; i < 3; ++i) { string s = getHex((int)(*(p + i) + 1)); cout << "a数组的第" << i << "行第1个元素的地址是" << s << endl; } cout << "指针数组" << endl; for (int i = 0; i < 3; ++i) { string s = getHex((int)(*(q + i) + 2)); cout << "a数组的第" << i << "行第2个元素的地址是" << s << endl; } return 0; }
执行结果
F2C8,F2CC,F2D0,F2D4 F2D8,F2DC,F2E0,F2E4 F2E8,F2EC,F2F0,F2F4 1. 指针 + i 数组指针 第0个[4]数组的地址是F2C8 第1个[4]数组的地址是F2D8 第2个[4]数组的地址是F2E8 指针数组(没啥用) 第0个q元素的地址是F378 第1个q元素的地址是F380 第2个q元素的地址是F388 2. *(指针 + i) 数组指针 a数组的第0行第0个元素的地址是F2C8 a数组的第1行第0个元素的地址是F2D8 a数组的第2行第0个元素的地址是F2E8 指针数组 a数组的第0行第0个元素的地址是F2C8 a数组的第1行第0个元素的地址是F2D8 a数组的第2行第0个元素的地址是F2E8 3. *(指针 + i) + j 数组指针 a数组的第0行第1个元素的地址是F2CC a数组的第1行第1个元素的地址是F2DC a数组的第2行第1个元素的地址是F2EC 指针数组 a数组的第0行第2个元素的地址是F2D0 a数组的第1行第2个元素的地址是F2E0 a数组的第2行第2个元素的地址是F2F0
指针和函数
指针传参
#include <iostream> using namespace std; // 函数的值传递 void swap(int a, int b) { cout << "调用函数后 a 的地址 = " << &a << endl; cout << "调用函数后 b 的地址 = " << &b << endl; int tmp = a; a = b; b = tmp; } // 函数的址传递 void swap1(int *a, int *b) { cout << "调用函数后 a 的地址 = " << a << endl; cout << "调用函数后 b 的地址 = " << b << endl; cout << "----" << endl; int tmp = *a; // a解引用后赋值给tmp cout << "调用函数后 a 的值 = " << *a << endl; cout << "调用函数后 b 的值 = " << *b << endl; cout << "调用函数后 tmp 的值 = " << tmp << endl; cout << "----" << endl; *a = *b; cout << "调用函数后 a 的值 = " << *a << endl; cout << "调用函数后 b 的值 = " << *b << endl; cout << "调用函数后 tmp 的值 = " << tmp << endl; cout << "----" << endl; *b = tmp; cout << "调用函数后 a 的值 = " << *a << endl; cout << "调用函数后 b 的值 = " << *b << endl; cout << "调用函数后 tmp 的地址 = " << tmp << endl; cout << "----" << endl; } int main() { int a = 1; int b = 2; cout << "调用函数前 a 的地址 = " << &a << endl; cout << "调用函数前 b 的地址 = " << &b << endl; //swap(a, b); swap1(&a, &b); cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "----" << endl; return 0; }
指针函数
返回值是指针的函数。
#include <iostream> using namespace std; // 指针函数 int* func() { return NULL; } // 场景:有一个函数想返回数组时,就可以用指针函数了 // 只需要返回这个数组的首地址 // 例:返回一个n个元素的数列,数列以x开始,公差为d的等差数列 int* getArray(int a, int d, int n) { // a 首项, d 公差, n 元素个数 int* ret = new int[n]; // ret 代表要返回的首地址。申请动态内存空间 // ret[0] = a; // ret[1] = a + d; // ... // ret[i] = a + i * d; for (int i = 0; i < n; ++i) { ret[i] = a + i * d; } return ret; } int main() { int* ans = getArray(5, 3, 6); for (int i = 0; i < 6; ++i) { //cout << ans[i] << endl; cout << *(ans + i) << endl; // *(ans + i) == ans[i] } return 0; }
函数指针
指向函数的指针。
#include <iostream> using namespace std; // 函数指针 double (*ptr)(int a, int b, int c); void (*ptr1)(int a, int b); double func(int a, int b, int c) { cout << a << ',' << b << ',' << c << endl; return 0.0; } void func1(int a, int b) { cout << a << ',' << b << endl; } int main() { ptr = func; ptr(1, 2, 3); //ptr = func1; 错误。函数指针在赋值时,必须参数匹配、返回值匹配 ptr1 = func1; ptr1(5, 6); return 0; }
函数指针类型定义
#include <iostream> using namespace std; // 函数指针的定义 void (*fptr1)(int a, int b, int c, float d, char e); void (*fptr2)(int a, int b, int c, float d, char e); void func1(int a, int b, int c, float d, char e) { cout << "func1" << endl; } // 函数指针的类型定义 typedef void (*fptr)(int a, int b, int c, float d, char e); int main() { fptr1 = func1; fptr1(1, 2, 3, 4, 5); fptr2 = func1; fptr2(9, 8 , 7, 6, 5); fptr fp1 = func1; // fptr 自定义类型 // int x = 6; 格式和这个一样 fp1(6, 7, 8, 9, 10); return 0; }
函数指针数组
实际上就是数组,数组中每个元素都是函数指针。
#include <iostream> using namespace std; // 函数指针数组 // [函数指针1, 函数指针2, ...] //typedef void (*fptrs[])(int a, int b, double c, float d, char e); 可以为空 typedef void (*fptrs[56])(int a, int b, double c, float d, char e); typedef void (*fptr)(int a, int b, double c, float d, char e); void func1(int a, int b, double c, float d, char e) { cout << "func1" << endl; } void func2(int a, int b, double c, float d, char e) { cout << "func2" << endl; } void func3(int a, int b, double c, float d, char e) { cout << "func3" << endl; } int main() { // int a[] = {1, 2, 3}; fptrs fps = { func1, func2, func3 }; cout << fps[0] << endl; cout << fps[1] << endl; cout << fps[2] << endl; cout << fps[3] << endl; fptr fp[] = {func1, func2, func3}; cout << fp[0] << endl; cout << fp[1] << endl; cout << fp[2] << endl; //cout << fp[3] << endl; return 0; }
练习
手机短号
/* HDOJ 2081 手机短号 https://acm.hdu.edu.cn/showproblem.php?pid=2081 输入:输入 t 组数据,每组数据是一个11位的手机号 输出:每组数据输出一个6,再跟上手机号的末五位 */ #include <iostream> using namespace std; // s+6 // s = 123456 78901 int main() { int t; cin >> t; while (t--) { char s[12]; cin >> s; cout << '6' << s + 6 << endl; } return 0; }
整除的尾数
/* HDOJ 2099 整除的尾数 https://acm.hdu.edu.cn/showproblem.php?pid=2099 */ #include <iostream> using namespace std; // a=200, b=40, 返回值[00, 40, 80], *returnSize = 3 int* calcTail(int a, int b, int* returnSize) { // 1、返回值代表结果数组的首地址,returnSize 是外部传进来的一个地址 *returnSize = 0; // 2、对外部传进来的数组大小,进行初始化 int* ret = new int[100]; // 3、申请一个数组内存,最多100个元素 for (int i = 0; i < 100; ++i) { if ((a * 100 + i) % b == 0) { // 4、枚举所有的末两位,并且判断是否整除,如果整除则塞入结果数组 ret[(*returnSize)++] = i; // 5、(*returnSize)++ 代表将数组的大小 + 1 } } return ret; } int main() { int a, b; while (cin >> a >> b) { if (!a && !b) { break; } int size; int* ret = calcTail(a, b, &size); for (int i = 0; i < size; ++i) { if (i) { cout << " "; } if (ret[i] < 10) { cout << "0"; } cout << ret[i]; } cout << endl; delete[] ret; // 数据用完,把它的内存回收掉,不然就内存泄漏了 } return 0; }
结构体
结构体的定义和使用
#include <iostream> #include <string> using namespace std; // 1. 结构体定义 // struct 结构体名 { 结构体成员变量列表 }; // 在c++中结构体在定义时,struct关键字是不能省略。在创建结构体变量时可以省略 struct Book { string name; double price; int value; }; struct Apple { string name; double price; int value; }cpp; // 2. 创建结构体 int main() { // 2.1 Book c; // 创建c语言的书 c.name = "C语言程序设计"; c.price = 6.6; c.value = 10; cout << c.name << ' ' << c.price << ' ' << c.value << endl; // 2.2 通过初始化列表方式创建 Book py = { "Python编程", 1999, 10 }; cout << py.name << ' ' << py.price << ' ' << py.value << endl; // 2.3 直接在结构体定义完后加上 cpp.name = "C++零基础编程"; cpp.price = 9999; cpp.value = 100000; cout << cpp.name << ' ' << cpp.price << ' ' << cpp.value << endl; return 0; }
结构体数组
#include <iostream> #include <string> using namespace std; // 1. 结构体定义 // struct 结构体名 { 结构体成员变量列表 }; // 在c++中结构体在定义时,struct关键字是不能省略。在创建结构体变量时可以省略 struct Book { string name; double price; int value; }cpp; int main() { // 2. 创建一个结构体数组 // 结构体名 数组名[元素个数] = { {}, {}, ...}; Book books[3] = { {"C语言程序设计", 199.99, 7}, {"Python零基础",399.99, 9}, {"C++零基础", 39.99, 10000} }; // 修改 books[2].name = "C++从入门到入土"; // 查找 for (int i = 0; i < 3; ++i) { cout << books[i].name << ' ' << books[i].price << ' ' << books[i].value << endl; } return 0; }
结构体指针
#include <iostream> #include <string> using namespace std; // 1. 结构体定义 // struct 结构体名 { 结构体成员变量列表 }; // 在c++中结构体在定义时,struct关键字是不能省略。在创建结构体变量时可以省略 struct Book { string name; double price; int value; }cpp; int main() { Book b = { "C语言", 99.99, 7 }; Book c = b; // 拷贝 c.name = "C语言入门"; cout << b.name << ' ' << b.price << ' ' << b.value << endl; // 结构体指针 Book* pb = &b; pb->name = "C++"; // 当是结构体指针时,就不能用.点来访问,要使用箭头 -> // pb拿到了b的地址,改pb时,相当于改b,所以下面输出是一样的。 cout << b.name << ' ' << b.price << ' ' << b.value << endl; cout << pb->name << ' ' << pb->price << ' ' << pb->value << endl; return 0; }
嵌套结构体
#include <iostream> using namespace std; struct Point { double x, y; }; struct Circle { Point pt; double radius; }; struct Circles { int size; Circle c[100]; }; int main() { Circle c; c.pt.x = 9; c.pt.y = 8; c.radius = 5; Circles cs = { 2, { {{9,8}, 5}, {{2,1}, 1} } }; for (int i = 0; i < cs.size; ++i) { Circle tmp = cs.c[i]; cout << "(" << tmp.pt.x << "," << tmp.pt.y << ")" << tmp.radius << endl; } return 0; }
结构体传参
#include <iostream> using namespace std; struct Point { double x, y; }; struct Circle { Point pt; double radius; }; // 打印圆 void printCircle(const Circle* c) { // 这个做好处:1、如果传的是对象,不是指针的话,那么每次调用这个函数都会进行一次拷贝。拷贝有消耗,传地址没消耗 // 2、它是个只读函数,c不能变 // c->pt.x += 1; 报错,常量不可修改 cout << "(" << c->pt.x << "," << c->pt.y << ") " << c->radius << endl; } // 实现一个函数:传入一个圆,让圆往x正方向偏移一个位置,往y的负方向偏移两个位置 void moveCircle(Circle *c, int x, int y) { //cout << &c << endl; c->pt.x += x; c->pt.y += y; } int main() { Circle c = { {9, 8}, 5 }; //cout << &c << endl; moveCircle(&c, 1, -2); printCircle(&c); return 0; }
练习
Rectangles
/* HDOJ 2056 Rectangles https://acm.hdu.edu.cn/showproblem.php?pid=2056 */ #include <iostream> using namespace std; struct Point { double x, y; }; struct Rect { Point lt; // 左上角矩形的坐标 Point rd; // 右下角矩形的坐标 }; Rect tmp1, tmp2; Rect r1, r2; double Max(double a, double b) { return a > b ? a : b; } double Min(double a, double b) { return a < b ? a : b; } int main() { while (cin >> tmp1.lt.x >> tmp1.lt.y >> tmp1.rd.x >> tmp1.rd.y) { cin >> tmp2.lt.x >> tmp2.lt.y >> tmp2.rd.x >> tmp2.rd.y; r1.lt.x = Min(tmp1.lt.x, tmp1.rd.x); r1.lt.y = Min(tmp1.lt.y, tmp1.rd.y); r1.rd.x = Max(tmp1.lt.x, tmp1.rd.x); r1.rd.y = Max(tmp1.lt.y, tmp1.rd.y); r2.lt.x = Min(tmp2.lt.x, tmp2.rd.x); r2.lt.y = Min(tmp2.lt.y, tmp2.rd.y); r2.rd.x = Max(tmp2.lt.x, tmp2.rd.x); r2.rd.y = Max(tmp2.lt.y, tmp2.rd.y); // 把矩形相交转化为区间相交 double maxx = Max(r1.lt.x, r2.lt.x); double minx = Min(r1.rd.x, r2.rd.x); double maxy = Max(r1.lt.y, r2.lt.y); double miny = Min(r1.rd.y, r2.rd.y); double ans = (minx - maxx) * (miny - maxy); // 面积 x * y if (minx < maxx) ans = 0; if (miny < maxy) ans = 0; printf("%.2lf\n", ans); } return 0; }
今年暑假不AC
/* HDOJ 2037 今年暑假不AC https://acm.hdu.edu.cn/showproblem.php?pid=2037 输入:反复输入一个n,代表有n个区间,然后输入n个区间 输出:输出选择最多的区间数,保证所有的区间都不重叠 */ #include <iostream> #include <algorithm> using namespace std; struct Interval { int s; int e; }I[100]; // 定义100个元素的结构体数组 bool cmp(const Interval& a, const Interval& b) { // 表示C++中的引用 return a.e < b.e; } int main() { int n; while (cin >> n && n) { for (int i = 0; i < n; ++i) { cin >> I[i].s >> I[i].e; } sort(I, I + n, cmp); int end = -1; // 上一个被选择的结束区间在哪里 int ans = 0; // 总共选了多少个区间 for (int i = 0; i < n; ++i) { if (I[i].s >= end) { // 开始点大于等于结束点,选进来 ans++; end = I[i].e; // 更新最新的结束点 } } cout << ans << endl; } return 0; }
联合体
定义和使用
联合体,又叫共用体。在C++中用union关键字表示。
#include <iostream> using namespace std; struct DataS { int i; double d; char s[10]; }; union DataU { int i; // 占用4个字节内存 double d; // 8 char s[10]; // 10 }; // 总共这个union占用10个字节内存,但由于字节对齐,实际上占用16个字节内存 // 区别 // 结构体每个成员都有单独的内存,相互之间没有影响。 // 联合体所有成员都是共享内存的,占用的是同一段内存 // 对于联合体,所有成员都从相同的内存地址开始 /* 1、定义和使用分开 union DataU { int i; // 占用4个字节内存 double d; // 8 char s[10]; // 10 }; DataU a, b, c; 2、定义和使用结合 union DataU { int i; // 占用4个字节内存 double d; // 8 char s[10]; // 10 }a, b, c; 3、匿名:不想让别人使用 union { int i; // 占用4个字节内存 double d; // 8 char s[10]; // 10 }a, b, c; */ int main() { DataS ds; cout << &ds.i << "," << &ds.d << "," << (void*)ds.s << endl; DataU du; cout << &du.i << "," << &du.d << "," << (void*)du.s << endl; return 0; }
000000C66C5DF728,000000C66C5DF730,000000C66C5DF738 000000C66C5DF768,000000C66C5DF768,000000C66C5DF768
字节对齐
- 结构体变量的首地址是最长成员长度的整数倍。
- 每个成员相对结构体首地址的偏移量,一定是该成员长度的整数倍。
- 结构体的总长度是最长成员长度的整数倍。
- 如果结构体内有成员长度大于处理器的位数,那么就以处理器的位数作为对齐单位。
内存布局
#include <iostream> using namespace std; union DataU { int i; // 4 double d; // 8 char s[7]; // 7 }; int main() { cout << sizeof(DataU) << endl; // 8 DataU du; du.s[0] = 255; // 在内存中 11111111 du.s[1] = 1; // 00000001 du.s[2] = 0; // 00000000 du.s[3] = 0; // 00000000 cout << du.i << endl; // 00000000 00000000 00000001 11111111, 511 // 上面涉及字节序、大小端问题 du.i = 256; cout << (int)du.s[0] << (int)du.s[1] << (int)du.s[2] << (int)du.s[3] << endl; // 0100 其中s return 0; }
字节序
字节序(Endianness)是数据在计算机内存中存储的顺序方式。
- 计算机将数据按字节(8位)进行存储,不同的系统可能会有不同的字节存储顺序
大端序(BigEndian)
- 定义:在大端序中,数据的高位字节存储在内存的低地址处,低位字节存储在高地址处。
例子:假设我们要存储一个32位整数0x12345678,它在内存中的存储顺序是:
- 地址0X00:0x12
- 地址0X01:0x34
- 地址0X02:0x56
- 地址0X03:0x78
小端序(LittleEndian)
- 定义:在小端序中,数据的低位字节存储在内存的低地址处,高位字节存储在高地址处
例子:假设我们要存储一个32位整数0x12345678,它在内存中的存储顺序是:
- 地址0X00:0x78
- 地址0X01:0x56
- 地址OXO2:0x34
- 地址OX03:0x12
网络字节序(Network ByteOrder)
- 定义:网络字节序是为了确保在网络中不同计算机间传输数据时,能够统一数据的存储顺序。它采用的是大端序。
- 说明:即使发送方和接收方的计算机采用不同的字节序,网络手字节序确保了数据传输的正确性。通常使用网络协议(如TCP/I(P)进行数据传输时,都要求将数据转换为网络字节序。
为什么需要字节序?
- 跨平台兼容性:不同架构的计算机可能使用不同的字节序方式。为了确保数据在不同机器间传输时不会发生错误,需要有统一的字节序标准。
- 网络通信:通过网络传输数据时,不同计算机可能采用不同的字书节序,使用网络字节序可以确保数据正确传输。
- 硬件差异:一些硬件平台(如X86、ARM)采用小端序,而一些平台(如某些RISC系统)采用大端序,标准化字节序能够提高兼容性。
如何确定系统的字节序?
#include <iostream> using namespace std; int main() { int num = 1; char* ptr = (char*)# if (*ptr == 1) { cout << "小端序(Little Endian)" << endl; } else { cout << "大端序(Big Endian)" << endl; } return 0; }
原理:将一个整数值1存储到内存中,使用char*类型的指针访问内存。当系统是小端序时,低字节(1)会存储在低地址处,输出值为1。如果是大端序,则高字节(1)会存储在低地址处,输出值为0。
字节序转换
- 在编程中,为了保证数据的正确性,可以使用一些库函数来进行字节序转换。例如,htonl()和ntohl()用于将数据从主机字 节序转换为网络字节序,反之亦然。
- htonl():主机到网络字节序(32位)
- ntohl():网络到主机字节序(32位)
总结
- 大端序:高位字节在低地址,易于人类阅读。
- 小端序:低位字节在低地址,X86等主流平台使用。
- 网络字节序:统一使用大端序,确保数据在网络中的正确传输。
参考:
应用
通过不同的类型存储不同的数据,又因为这个数据不同,又希望内存不要浪费,所以把它们存在这个union中。
#include <iostream> using namespace std; struct Info { char _name[20]; int _role; // 角色 老师、学生 union { double score; // 学生的分数 char course[20]; // 老师的课 }_sc; // 定义构造函数 Info(const char name[20], int role, double s, const char c[20]) { strcpy_s(_name, name); // 把传入的参数拷贝给_name _role = role; if (s > 0) _sc.score = s; if (strlen(c) > 0) strcpy_s(_sc.course, c); }; }; int main() { Info a[4] = { Info("周老师", 0, -1, "c++"), Info("周老师", 0, -1, "Python"), Info("张同学", 1, 90, ""), Info("肖同学", 1, 88, ""), }; for (int i = 0; i < 4; ++i) { if (a[i]._role == 0) { cout << a[i]._name << "是一位老师,他是教" << a[i]._sc.course << "的" << endl; } else if (a[i]._role == 1) { cout << a[i]._name << "是一位学生,他的分数是" << a[i]._sc.score << endl; } } return 0; }
内存管理
代码区
所有写的代码都会放在这个代码区中,最终转换成01的二进制数据,由操作系统统一管理。
代码区特点
- 只读:代码区运行后就无法修改了
- 共享:当前运行多少进程时,它所在的代码区实际上是共享的。好处是节省内存空间。
#include <iostream> using namespace std; // 代码区、全局区、栈区、堆区 void printMessage() { cout << "Hello world!" << endl; } int main() { printMessage(); while(1) {} return 0; }
全局区
#include <iostream> using namespace std; // 全局区 // 主要存放:全局变量、全局常量、静态变量、字符串常量 // int g_a = 1; // 全局变量存储在全局区 int g_b = 2; const int c_g_a = 3; // 全局常量 const int c_g_b = 4; int main() { cout << "全局变量g_a的地址:" << &g_a << endl; cout << "全局变量g_b的地址:" << &g_b << endl; int c = 3; // 局部变量,实际上存储在栈区 int d = 4; static int e = 5; // 静态变量。与全局变量存在一起 static int f = 6; cout << "局部变量 c 的地址:" << &c << endl; cout << "局部变量 d 的地址:" << &d << endl; cout << "静态变量 e 的地址:" << &e << endl; cout << "静态变量 f 的地址:" << &f << endl; // 字符串常量 cout << "字符串常量的地址: " << &"你好" << endl; const int g = 7; // 局部常量存储在栈区 const int h = 8; cout << "局部常量 g 的地址:" << &g << endl; cout << "局部常量 h 的地址:" << &h << endl; cout << "全局常量c_g_a的地址:" << &c_g_a << endl; cout << "全局常量c_g_b的地址:" << &c_g_b << endl; return 0; }
执行结果
全局变量g_a的地址:00007FF60507E048 全局变量g_b的地址:00007FF60507E04C 局部变量 c 的地址:000000958E9CFB24 局部变量 d 的地址:000000958E9CFB44 静态变量 e 的地址:00007FF60507E050 静态变量 f 的地址:00007FF60507E054 字符串常量的地址: 00007FF60507AC64 局部常量 g 的地址:000000958E9CFB64 局部常量 h 的地址:000000958E9CFB84 全局常量c_g_a的地址:00007FF60507ABDC 全局常量c_g_b的地址:00007FF60507ABE0
栈区
栈区和堆区的内存都是程序运行过程中申请和释放的。
区别是栈区的内存是由操作系统来控制生命周期的,而堆区的内存是由程序员来控制生命周期的。比如是函数的参数、局部变量都是栈区的内存。
这里的堆和栈和数据结构中的堆和栈是两个概念。
#include <iostream> using namespace std; char* func() { char c[20] = "英雄哪里出来"; return c; } void test(int a, int b) { int c, d; cout << "形式参数a的地址:" << &a << endl; cout << "形式参数b的地址:" << &b << endl; cout << "局部变量c的地址:" << &c << endl; cout << "局部变量d的地址:" << &d << endl; } int main() { cout << func() << endl; test(5, 6); return 0; }
执行结果
烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫 形式参数a的地址:0000008ADCEFFC60 形式参数b的地址:0000008ADCEFFC68 局部变量c的地址:0000008ADCEFFB44 局部变量d的地址:0000008ADCEFFB64
堆区
堆区的内存是由程序员来申请和释放的。
- C语言中堆区申请和释放用malloc和free;
- C++中堆区申请用new,释放用delete;
- 区别是malloc, free是函数、new,delete是运算符。
#include <iostream> using namespace std; // C malloc free // C++ new delete int* getV(int v) { int* a = new int(v); // int* a 是栈上变量; cout << a << endl; // *a 也就是a解引用得到的值,是存储在堆中的 return a; // 函数返回的时候,虽然栈上的变量a被操作系统释放了,但是a指向的内存,还是存在的 } int main() { int* p = getV(1314); cout << *p << endl; // 所以,这里还是可以解引用,得到堆上的数据的 cout << p << endl; return 0; }
new/delete
内存申请释放
// new和delete #include <iostream> using namespace std; int main() { // new int 在堆中找一块整数的空间并把内存分配出来,并返回一个指向这块内存的一个指针; // 所以我们可以定义一个ptr指向它 int* ptr = new int; cout << *ptr << endl; // 这块内存中的值是未定义的,可能是一些随机的值 int* ptr1 = new int(1314); // 这个小括号就是把数据进行初始化 cout << *ptr1 << endl; *ptr = 520; cout << *ptr << endl; delete ptr; delete ptr1; ptr = NULL; // 其它人可以判断它是否为空 ptr1 = NULL; return 0; }
new 数据的初始化
数组申请释放
#include <iostream> using namespace std; // 返回一个数组,并且数组中的元素是相邻两个数的差值 int* getGapList(int* arr, int size) { int* p = new int[size - 1]; // int a[size - 1]; return a; 不行,a是栈上内存,不能作为返回值返回 for (int i = 0; i < size - 1; ++i) { p[i] = arr[i + 1] - arr[i]; } return p; } int main() { int arr[] = { 1,5,6,4,4,3,3,2,1,9 }; int* p = getGapList(arr, 10); for (int i = 0; i < 9; ++i) { cout << p[i] << " "; } cout << endl; delete[] p; // 不加[],只删除数组首地址 p = NULL; return 0; }
引用
引用的语法
引用能够实现原本指针的功能。C语言没有引用,它会比指针更好理解。
引用实际上是给变量取了一个别名。
#include <iostream> using namespace std; // 指针:所以爱会消失,对嘛? // 引用:给变量取一个别名 // 符号 & // 数据类型& 变量名 = 变量; void test() { int a_very_very_very_very_very_very_very[8] = { 1, 1 }; for (int i = 2; i < 8; ++i) { // 等于 前一个数的平方 + 前两个数的平方 a_very_very_very_very_very_very_very[i] = a_very_very_very_very_very_very_very[i - 1] * a_very_very_very_very_very_very_very[i - 1] + a_very_very_very_very_very_very_very[i - 2] * a_very_very_very_very_very_very_very[i - 2]; } for (int i = 0; i < 8; ++i) { cout << a_very_very_very_very_very_very_very[i] << " "; } cout << endl; for (int i = 2; i < 8; ++i) { a_very_very_very_very_very_very_very[i] = 0; } // 代码缩短 // 将 a_very_very_very_very_very_very_very 缓存起来 for (int i = 2; i < 8; ++i) { int& pre1 = a_very_very_very_very_very_very_very[i - 1]; int& pre2 = a_very_very_very_very_very_very_very[i - 2]; int& now = a_very_very_very_very_very_very_very[i]; now = pre1 * pre1 + pre2 * pre2; } for (int i = 0; i < 8; ++i) { cout << a_very_very_very_very_very_very_very[i] << " "; } cout << endl; } int main() { /* 指针方式 多写3个星号 int a = 1314; int* b = &a; // a和b是同一个值 *b = 520; cout << "a = " << a << endl; cout << "b = " << *b << endl; */ int a = 1314; int& b = a; // a和b是同一个值 b = 520; cout << "a = " << a << endl; cout << "b = " << b << endl; test(); return 0; }
执行结果
a = 520 b = 520 1 1 2 5 29 866 750797 1056169389 1 1 2 5 29 866 750797 1056169389
引用的特性
- 必须初始
- 初始化以后无法修改
引用比指针更加安全,避免了指针可能出现的未初始化问题。不会有空指针出现。
#include <iostream> using namespace std; // 1、必须初始 // 2、初始化以后无法修改 int main() { // int& a; 错误写法 int a = 3, c = 6; int& b = a; b = c; // 实际上是 b = 6; 不代表把 b 变成 c 的引用,而只是把变量 c 的值赋值给了 b cout << a << b << c << endl; return 0; }
引用的赋值
引用的本质
引用的底层实现就是一个指针常量。指针常量就是指针的值是一个常量,即指针所指的地址不可改变,一旦初始化无法修改。
#include <iostream> using namespace std; // 引用 解引用(把引用中的值解出来) int main() { int a = 520; //int& b = a; // 00007FF6791F1FD5 lea rax,[a] // 00007FF6791F1FD9 mov qword ptr[b], rax //b = 1314; // 00007FF6791F1FDD mov rax,qword ptr [b] // 00007FF6791F1FE1 mov dword ptr[rax], 522h int* const b = &a; // 00007FF6791F1FD5 lea rax,[a] // 00007FF6791F1FD9 mov qword ptr[b], rax *b = 1314; // 00007FF6791F1FDD mov rax, qword ptr[b] // 00007FF6791F1FE1 mov dword ptr[rax], 522h return 0; }
vs 中菜单栏“调试” –> ”窗口“ –>”“反汇编”
引用作为传参
#include <iostream> using namespace std; // 统计数组中,有多少个值为2的元素,并且返回它的和 int countAndSum(int arr[], int size, int target, int& count) { int sum = 0; cout << &count << endl; for (int i = 0; i < size; ++i) { if (arr[i] == target) { count++; sum += arr[i]; } } return sum; } struct S { int a, b, c,d,e,f,g; }; /* void printS(S s) { cout << &s << endl; cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << endl; } */ void printS(S& s) { // 结构体作为参数时,是拷贝了一份新数据出来。加上引用就可以避免这次拷贝 cout << &s << endl; cout << s.a << s.b << s.c << s.d << s.e << s.f << s.g << endl; } int main() { int arr[] = { 1, 2, 3, 2, 4, 5, 6, 4, 3, 2 }; // 10 int c = 0; cout << &c << endl; int sum = countAndSum(arr, 10, 2, c); cout << sum << ' '<< c << endl; S s = { 1, 2, 3, 4, 5, 6, 7 }; cout << &s << endl; printS(s); return 0; }
引用作为返回值
#include <iostream> using namespace std; int& getArrayValue(int arr[], int index) { return arr[index]; // 不加引用返回的是值。加了引用,返回的是arr[index]的一个别名 } int main() { int a[] = { 8, 7, 6, 5, 4, 3 }; cout << getArrayValue(a, 3) << endl; // 把3的值改变,又不想访问数组 getArrayValue(a, 3) = 999; // a[3] = 999; cout << getArrayValue(a, 3) << endl; return 0; }
执行结果
5 999
常量引用
常量引用使用非常广泛,尤其是在STL中。
#include <iostream> #include <vector> using namespace std; struct S { int a, b, c, d, e, f; }; void printS(const S& s) { // 引用不需要再拷贝一份数据进来,为避免修改加一个const修饰 // s.b = 520; cout << s.a << s.b << s.c << s.d << s.e << s.f << endl; } int main() { int a; const int& b = a; // 引用 = 指针常量 // 常量引用 = 常量指针常量 S s = { 1, 2, 3, 4, 5, 6 }; printS(s); //vector<int> a; return 0; }
指针引用
#include <iostream> using namespace std; // *& void allocMemory1(char* ptr, int bytes) { // 这个ptr是局部变量 ptr = new char[bytes]; cout << "ptr的地址:" << &ptr << endl; } void test1() { char* p = NULL; allocMemory1(p, 5); cout << (void*)p << endl; cout << "p的地址:" << &p << endl; } void allocMemory2(char*& ptr, int bytes) { // 加上引用后ptr就变成p的别名了。指针引用还是个引用 ptr = new char[bytes]; cout << "ptr的地址:" << &ptr << endl; } void test2() { char* p = NULL; allocMemory2(p, 5); cout << (void*)p << endl; cout << "p的地址:" << &p << endl; } int main() { //test1(); test2(); return 0; }
项目-贪食蛇
游戏循环
计算机游戏的本质,其实是玩家和计算机交互的过程,玩家通过鼠标或者键盘,给计算机一些输入,让计算机计算完毕以后,输出相应的结果,并且是反复交互的过程。那么这里提到了反复,所以我们需要有一个循环,来完成这件 "反复" 的事情。
所以在main函数里,我们先写一个 while(1),代表后续所有的游戏逻辑,都在这个 while(1) 中执行。并且可以在里面打印出一句话:"游戏正在执行中……",运行一下看下效果。
所有的游戏,实际上都是一个循环,只不过在循环内部,执行不同的逻辑而已,接下来,我们逐渐往里面填充内容。
#include <iostream> using namespace std; int main() { while (1) { } return 0; }
地图绘制
贪食蛇的话,肯定有一张地图,蛇是在地图里进行游走的,地图肯定有一个边界,所以我们想办法,先把这个边界绘制出来。利用两个 for 循环的嵌套就可以了。
#include <iostream> using namespace std; #define H 28 #define W 60 void drawMap() { system("cls"); cout << "┏"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┓" << endl; for (int y = 0; y < H; ++y) { cout << "┃"; for (int x = 0; x < W; ++x) { cout << " "; } cout << "┃" << endl; } cout << "┗"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┛"; } int main() { drawMap(); while (1) { } return 0; }
- drawMap(Map* map):绘制地图边框,内部为空白。使用系统命令"cls"清屏,然后绘制顶部边框,接着每一行绘制左右边框,最后绘制底部边框。注意,这里只绘制了边框,地图内部空白处用空格填充。
知识点:常量定义、整型、运算符、字符串输出、嵌套 for 循环、函数的定义、函数的调用
光标隐藏
#include <iostream> #include <Windows.h> using namespace std; #define H 28 #define W 60 void hideCursor() { HANDLE hOutpt = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO curInfo = { 1, FALSE }; SetConsoleCursorInfo(hOutpt, &curInfo); } void drawMap() { system("cls"); cout << "┏"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┓" << endl; for (int y = 0; y < H; ++y) { cout << "┃"; for (int x = 0; x < W; ++x) { cout << " "; } cout << "┃" << endl; } cout << "┗"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┛"; } int main() { hideCursor(); drawMap(); while (1) { } return 0; }
Windows.h用于调用Windows API来隐藏控制台光标和设置光标位置。
知识点:指针取地址、系统函数的调用、函数的定义、函数的调用
地图定义
主要是定义地图的结构体。
#include <iostream> #include <Windows.h> using namespace std; #define H 28 #define W 60 enum BlockType { EMPTY = 0, FOOD = 1, }; struct Map { BlockType data[H][W]; bool hasFood; }; void hideCursor() { HANDLE hOutpt = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO curInfo = { 1, FALSE }; SetConsoleCursorInfo(hOutpt, &curInfo); } void initMap(Map* map) { for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { map->data[y][x] = BlockType::EMPTY; } } map->hasFood = false; } void drawMap(Map* map) { system("cls"); cout << "┏"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┓" << endl; for (int y = 0; y < H; ++y) { cout << "┃"; for (int x = 0; x < W; ++x) { if (map->data[y][x] == BlockType::EMPTY) { cout << " "; } } cout << "┃" << endl; } cout << "┗"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┛"; } int main() { Map map; hideCursor(); initMap(&map); drawMap(&map); while (1) { } return 0; }
- 枚举了地图块的类型,目前有空白和食物。
结构体定义:
- Map:包含一个二维数组data表示地图每个格子的类型,以及一个布尔值hasFood表示当前地图上是否有食物。
知识点:二维数组、枚举的定义、结构体的定义、指针的使用、结构体指针
蛇体定义
#include <iostream> #include <Windows.h> using namespace std; #define H 28 #define W 60 enum BlockType { EMPTY = 0, FOOD = 1, }; struct Map { BlockType data[H][W]; bool hasFood; }; struct Pos { int x; int y; }; struct Snake { Pos snake[H * W]; int snakeDir; int snakeLength; }; void hideCursor() { HANDLE hOutpt = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO curInfo = { 1, FALSE }; SetConsoleCursorInfo(hOutpt, &curInfo); } void initMap(Map* map) { for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { map->data[y][x] = BlockType::EMPTY; } } map->hasFood = false; } void initSnake(Snake* snk) { snk->snakeLength = 1; snk->snakeDir = 1; snk->snake[0] = { W / 2, H / 2 }; } void drawMap(Map* map) { system("cls"); cout << "┏"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┓" << endl; for (int y = 0; y < H; ++y) { cout << "┃"; for (int x = 0; x < W; ++x) { if (map->data[y][x] == BlockType::EMPTY) { cout << " "; } } cout << "┃" << endl; } cout << "┗"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┛"; } int main() { Map map; Snake snk; hideCursor(); initMap(&map); initSnake(&snk); drawMap(&map); while (1) { } return 0; }
结构体定义:
- Pos:表示位置,有x和y两个坐标。
- Snake:表示蛇,由一个位置数组(表示蛇的每一节)、蛇的移动方向和蛇的长度组成。
知识点:一维数组、结构体初始化、嵌套结构体、结构体创建、结构体指针
蛇体绘制
#include <iostream> #include <Windows.h> using namespace std; #define H 28 #define W 60 enum BlockType { EMPTY = 0, FOOD = 1, }; struct Map { BlockType data[H][W]; bool hasFood; }; struct Pos { int x; int y; }; struct Snake { Pos snake[H * W]; int snakeDir; int snakeLength; }; void hideCursor() { HANDLE hOutpt = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO curInfo = { 1, FALSE }; SetConsoleCursorInfo(hOutpt, &curInfo); } void initMap(Map* map) { for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { map->data[y][x] = BlockType::EMPTY; } } map->hasFood = false; } void initSnake(Snake* snk) { snk->snakeLength = 1; snk->snakeDir = 1; snk->snake[0] = { W / 2, H / 2 }; } void drawUnit(Pos p, const char unit[]) { COORD coord; HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); coord.X = p.x + 1; coord.Y = p.y + 1; SetConsoleCursorPosition(hOutput, coord); cout << unit; } void drawMap(Map* map) { system("cls"); cout << "┏"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┓" << endl; for (int y = 0; y < H; ++y) { cout << "┃"; for (int x = 0; x < W; ++x) { if (map->data[y][x] == BlockType::EMPTY) { cout << " "; } } cout << "┃" << endl; } cout << "┗"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┛"; } void drawSnake(Snake* snk) { for (int i = 0; i < snk->snakeLength; ++i) { drawUnit(snk->snake[i], "■"); } } int main() { Map map; Snake snk; hideCursor(); initMap(&map); initSnake(&snk); drawMap(&map); drawSnake(&snk); while (1) { } return 0; }
- drawUnit(Pos p, const char unit[]):在指定位置绘制一个单位(字符串),使用Windows API设置光标位置然后输出。
- drawSnake(Snake* snk):遍历蛇的每一节,调用drawUnit绘制蛇身(用"■"表示)。
让蛇体初始化长度为 3,确保算法正确:
void initSnake(Snake* snk) { snk->snakeLength = 3; snk->snakeDir = 1; snk->snake[0] = { W / 2, H / 2 }; snk->snake[1] = { W / 2 - 1, H / 2 }; snk->snake[2] = { W / 2 - 2, H / 2 }; }
知识点:系统函数调用、for 循环、输出
蛇体移动
加入方向常量:
const int dir[4][2] = { {-1, 0}, // 上 {0, 1}, // 右 {1, 0}, // 下 {0, -1} // 左 };
实现蛇体的移动:
void moveSnake(Snake* snk) { for (int i = snk->snakeLength - 1; i >= 1; --i) { snk->snake[i] = snk->snake[i - 1]; } snk->snake[0].y += dir[snk->snakeDir][0]; snk->snake[0].x += dir[snk->snakeDir][1]; } bool doMove(Snake* snk, Map* map) { Pos tail = snk->snake[snk->snakeLength - 1]; drawUnit(snk->snake[snk->snakeLength - 1], " "); moveSnake(snk); drawUnit(snk->snake[0], "■"); return true; } bool checkSnakeMove(Snake* snk, Map* map) { doMove(snk, map); return true; }
在游戏循环中,调用蛇体移动的接口。
int main() { Map map; Snake snk; hideCursor(); initMap(&map); initSnake(&snk); drawMap(&map); drawSnake(&snk); while (1) { if( false == checkSnakeMove(&snk, &map) ) { break; } } return 0; }
知识点:常量定义、复合赋值运算符、数组元素的插入、数组的索引、break、函数的定义、函数的调用、指针的使用
频率控制
增加两个用来控制频率的成员变量:
struct Snake { Pos snake[H * W]; int snakeDir; int snakeLength; int lastMoveTime; int moveFreqency; };
对成员变量进行初始化:
void initSnake(Snake* snk) { snk->snakeLength = 3; snk->snakeDir = 1; snk->snake[0] = { W / 2, H / 2 }; snk->snake[1] = { W / 2 - 1, H / 2 }; snk->snake[2] = { W / 2 - 2, H / 2 }; snk->lastMoveTime = 0; snk->moveFreqency = 200; }
进行频率控制:
bool checkSnakeMove(Snake* snk, Map* map) { int curTime = GetTickCount(); if (curTime - snk->lastMoveTime > snk->moveFreqency) { if (false == doMove(snk, map)) return false; snk->lastMoveTime = curTime; } return true; }
知识点:结构体成员、结构体指针的成员赋值、if 分支语句
边界检测
实现一个判断边界的接口,传参是一个 Pos 结构体:
bool checkOutOfBound(Pos p) { if (p.x == 0 || p.x == W + 1) { return true; } if (p.y == 0 || p.y == H + 1) { return true; } return false; }
在每次蛇体移动完毕以后,就调用这个接口进行判断,如果发现越界了,就返回 false。
bool doMove(Snake* snk, Map* map) { Pos tail = snk->snake[snk->snakeLength - 1]; drawUnit(snk->snake[snk->snakeLength - 1], " "); moveSnake(snk); if (checkOutOfBound(snk->snake[0])) { return false; } drawUnit(snk->snake[0], "■"); return true; }
知识点:比较运算符、逻辑或运算符、if 分支语句、函数的定义、函数的调用
游戏失败
break 跳出来以后,比较难看,所以我们希望输出一行字,代表游戏结束,如下:
...
while (1) {
if (false == checkSnakeMove(&snk, &map))
break;
}
drawUnit({ W / 2 - 4, H / 2 }, "Game Over");
while (1) {}
return 0;
}
知识点:结构体初始化、函数的调用、while 循环。
蛇体转向
引入一个新的头文件:
#include <conio.h>
实现一个控制蛇体转向的函数,利用 switch case 语句来实现,键盘的不同按键输入。
void checkChangeDir(Snake* snk) { if (_kbhit()) { switch (_getch()) { case 'w': if(snk->snakeDir != 2) snk->snakeDir = 0; break; case 'd': if (snk->snakeDir != 3) snk->snakeDir = 1; break; case 's': if (snk->snakeDir != 0) snk->snakeDir = 2; break; case 'a': if (snk->snakeDir != 1) snk->snakeDir = 3; break; default: break; } } }
然后调用它:
...
while (1) {
checkChangeDir(&snk);
if (false == checkSnakeMove(&snk, &map))
break;
checkFoodGenerate(&snk, &map);
}
drawUnit({ W/2-4, H/2 }, "Game Over");
while (1) {}
return 0;
}
知识点:switch case
食物生成
void checkFoodGenerate(Snake* snk, Map* map) { if (false == map->hasFood) { while (1) { int x = rand() % W; int y = rand() % H; int i = 0; while (i < snk->snakeLength) { if (snk->snake[i].x == x && snk->snake[i].y == y) { break; } ++i; } if (i == snk->snakeLength) { map->data[y][x] = BlockType::FOOD; map->hasFood = true; drawUnit({ x,y }, "●"); return; } } } }
调用食物生成的接口:
...
while (1) {
checkChangeDir(&snk);
if (false == checkSnakeMove(&snk, &map))
break;
checkFoodGenerate(&snk, &map);
}
drawUnit({ W / 2 - 4, H / 2 }, "Game Over");
while (1) {}
return 0;
}
知识点:while 嵌套、if 分支、取模运算符、逻辑与运算符、break
食物碰撞
实现一个吃食物的接口。
void checkEatFood(Snake* snk, Pos tail, Map* map) { Pos head = snk->snake[0]; if (map->data[head.y][head.x] == BlockType::FOOD) { snk->snake[snk->snakeLength++] = tail; map->data[head.y][head.x] = BlockType::EMPTY; map->hasFood = false; drawUnit(tail, "■"); } }
蛇每移动一步,就判断一次:
bool doMove(Snake* snk, Map* map) { Pos tail = snk->snake[snk->snakeLength - 1]; drawUnit(snk->snake[snk->snakeLength - 1], " "); moveSnake(snk); if (checkOutOfBound(snk->snake[0])) { return false; } checkEatFood(snk, tail, map); drawUnit(snk->snake[0], "■"); return true; }
知识点:比较运算符、if 语句、结构体数组的调用、递增运算符、枚举的使用
#include <iostream> #include <Windows.h> #include <conio.h> using namespace std; #define H 28 #define W 60 const int dir[4][2] = { {-1, 0}, // 上 {0, 1}, // 右 {1, 0}, // 下 {0, -1} // 左 }; enum BlockType { EMPTY = 0, FOOD = 1, }; struct Map { BlockType data[H][W]; bool hasFood; }; struct Pos { int x; int y; }; struct Snake { Pos snake[H * W]; int snakeDir; int snakeLength; int lastMoveTime; int moveFreqency; }; void hideCursor() { HANDLE hOutpt = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_CURSOR_INFO curInfo = { 1, FALSE }; SetConsoleCursorInfo(hOutpt, &curInfo); } void initMap(Map* map) { for (int y = 0; y < H; ++y) { for (int x = 0; x < W; ++x) { map->data[y][x] = BlockType::EMPTY; } } map->hasFood = false; } void initSnake(Snake* snk) { snk->snakeLength = 3; snk->snakeDir = 1; snk->snake[0] = { W / 2, H / 2 }; snk->snake[1] = { W / 2 - 1, H / 2 }; snk->snake[2] = { W / 2 - 2, H / 2 }; snk->lastMoveTime = 0; snk->moveFreqency = 200; } void drawUnit(Pos p, const char unit[]) { COORD coord; HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); coord.X = p.x + 1; coord.Y = p.y + 1; SetConsoleCursorPosition(hOutput, coord); cout << unit; } void drawMap(Map* map) { system("cls"); cout << "┏"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┓" << endl; for (int y = 0; y < H; ++y) { cout << "┃"; for (int x = 0; x < W; ++x) { if (map->data[y][x] == BlockType::EMPTY) { cout << " "; } } cout << "┃" << endl; } cout << "┗"; for (int x = 0; x < W; ++x) { cout << "━"; } cout << "┛"; } void drawSnake(Snake* snk) { for (int i = 0; i < snk->snakeLength; ++i) { drawUnit(snk->snake[i], "■"); } } void checkChangeDir(Snake* snk) { if (_kbhit()) { switch (_getch()) { case 'w': if (snk->snakeDir != 2) snk->snakeDir = 0; break; case 'd': if (snk->snakeDir != 3) snk->snakeDir = 1; break; case 's': if (snk->snakeDir != 0) snk->snakeDir = 2; break; case 'a': if (snk->snakeDir != 1) snk->snakeDir = 3; break; default: break; } } } bool checkOutOfBound(Pos p) { if (p.x == 0 || p.x == W + 1) { return true; } if (p.y == 0 || p.y == H + 1) { return true; } return false; } void moveSnake(Snake* snk) { for (int i = snk->snakeLength - 1; i >= 1; --i) { snk->snake[i] = snk->snake[i - 1]; } snk->snake[0].y += dir[snk->snakeDir][0]; snk->snake[0].x += dir[snk->snakeDir][1]; } void checkEatFood(Snake* snk, Pos tail, Map* map) { Pos head = snk->snake[0]; if (map->data[head.y][head.x] == BlockType::FOOD) { snk->snake[snk->snakeLength++] = tail; map->data[head.y][head.x] = BlockType::EMPTY; map->hasFood = false; drawUnit(tail, "■"); } } bool doMove(Snake* snk, Map* map) { Pos tail = snk->snake[snk->snakeLength - 1]; drawUnit(snk->snake[snk->snakeLength - 1], " "); moveSnake(snk); if (checkOutOfBound(snk->snake[0])) { return false; } checkEatFood(snk, tail, map); drawUnit(snk->snake[0], "■"); return true; } bool checkSnakeMove(Snake* snk, Map* map) { int curTime = GetTickCount(); if (curTime - snk->lastMoveTime > snk->moveFreqency) { if (false == doMove(snk, map)) return false; snk->lastMoveTime = curTime; } return true; } void checkFoodGenerate(Snake* snk, Map* map) { if (false == map->hasFood) { while (1) { int x = rand() % W; int y = rand() % H; int i = 0; while (i < snk->snakeLength) { if (snk->snake[i].x == x && snk->snake[i].y == y) { break; } ++i; } if (i == snk->snakeLength) { map->data[y][x] = BlockType::FOOD; map->hasFood = true; drawUnit({ x, y }, "●"); return; } } } } int main() { Map map; Snake snk; hideCursor(); initMap(&map); initSnake(&snk); drawMap(&map); drawSnake(&snk); while (1) { checkChangeDir(&snk); if (false == checkSnakeMove(&snk, &map)) break; checkFoodGenerate(&snk, &map); } drawUnit({ W / 2 - 4, H / 2 }, "Game Over"); while (1) {} return 0; }