0x00 变量与数据类型
变量必须先定义,才可以使用。不能重名。
变量定义的方式:
using namespace std;
int main() { int a = 5; int b, c = a, d = 10 / 2; cout << a << “ “ << b << endl;
return 0;
}
常用数据类型及范围:
| 类型 | 关键字 | 范围 | 备注 |
| --- | --- | --- | --- |
| 布尔型 | bool | | |
| 字符型 | char | 1字节<br />-128~127或0-255 | |
| 整型 | short int | 2字节 | 有符号 |
| 整型 | int | 4字节<br />![](https://cdn.nlark.com/yuque/__latex/959b596e4df9b8939f93d8836012bede.svg#card=math&code=%5B-2%5E%7B31%7D%2C%202%5E%7B31%7D%20-%201%5D&id=nijSD) | 有符号 |
| 整型 | long int | 4或8字节 | 有符号 |
| 整型 | long | 4或8字节 | 有符号 |
| 浮点型 | float | 4字节<br />6-7个有效数字 | 有符号 |
| 双浮点型 | double | 8字节<br />15-16个有效数字 | 有符号 |
| 无类型 | void | | |
| 宽字符型 | wchar_t | 2或4个字节 | `typedef short int wchar_t` |
| 长整型 | long long | 8字节<br />![](https://cdn.nlark.com/yuque/__latex/fdb47d20846b6dbfa226eec865db8e09.svg#card=math&code=%5B-2%5E%7B63%7D%2C%202%5E%7B63%7D%20-%201%5D&id=BiYNI) | 有符号 |
| 长双浮点型 | long double | 16字节<br />18-19位有效数字 | 有符号 |
<a name="bAKOh"></a>
## 0x01 输入输出
`cin`和`cout`在`iostream`头文件中<br />`scanf`和`printf`在`cstdio`头文件中
> 在读入字符时,`cin`会过滤空格,`scanf`不会过滤空格
使用`printf`时:<br />`int`型用`%d`, `long long`型用`%lld`<br />`float`型用`%f`, `double`型用`%lf`<br />用`float, double`等输出保留若干小数时用:`%.4f, %.3lf`<br />最小数字宽度:<br />`%8.3f`表示这个浮点数的最小宽度为8,保留3位小数,不足宽度时补空格<br />`%-8.3f`左对齐<br />`%08.3f`宽度不足时补上0<br />带符号:`%+lf`指明正负号<br />`%`输出时需要用`%%`
<a name="Ecr9G"></a>
## 0x02 运算符及表达式
类似于Java<br />不同点:
- 从高精度到低精度转换可以不用强制类型转换(当然用了也是对的)
<a name="xyuv4"></a>
## 0x03 判断语句
类似于Java<br />不同点:
- `switch`语句不用用`string`作为条件
<a name="UR7ad"></a>
## 0x04 循环语句
类似于Java
<a name="c2M1T"></a>
## 0x05 数组
数组的定义:<br />与变量定义类似,`int a[20]`<br />数组的初始化(与Java区别蛮大的):
- `int a[3] = {0, 1, 3}`
- `int b[] = {4, 5, 6}`
- `int c[5] = {1, 2, 3}`定义了一个长度为5的数组`{1, 2, 3, 0, 0}`
- `int d[10] = {0}` 将数组全部初始化为0
:::danger
Java的数组有默认初始化,Java中new出来的都在堆中<br />C++的数组如果定义在函数内,则没有默认初始化<br />C++函数内的变量,包括数组都在**栈中,只有定义在外部才在堆中,且包含初始化。**
:::
高维数组定义:<br />`int a[10][12]`<br />高维数组初始化:
- `int a[3][4] = {{1, 2, 3, 4}, {2, 3, 4, 5}, {3, 4, 5, 6}}`
快速初始化<br />`memset(a, 0, sizeof a)`含义是将从a开始的长度为`sizeof a`字节的空间初始化为0。`sizeof`位于`cstring`库中。<br />拷贝<br />`memcpy(b, a, sizeof a)`含义是将a数组整体复制到b数组。注意是**按字节顺序**赋值。
<a name="UWHYr"></a>
## 0x06 字符串
字符数组:<br />字符串就是字符数组加上结束符'\0'<br />可以使用字符串来初始化字符数组,但要注意每个字符串的结尾会暗含一个'\0',因此字符数组的长度至少比字符串长度大1<br />合法的字符数组初始化:
- `char a1[] = {'C', '+', '+'}`
- `char a2[] = {'C', '+', '+', '\0'}`
- `char a3[] = "C++"`
非法初始化:`char a4[3] = "C++"`
字符数组当字符串输入:
- `scanf("%s", a1)`
- `cin >> a2`
- `cin >> a3 + 1`
:::success
输入字符串时,碰到回车或空格就会停止
:::
问题:如何读入一行?<br />`fgets(a, 100, stdin)`表示从标准输入中读取不超过100个字符的一行数据到字符数组`a`中。**会读取末尾的回车,如果有的话**。<br />`cin.getline(a, 100)`表示从标准输入中读取不超过100个字符的一行数据到字符数组`a`中。**不会读取末尾的回车**。
字符数组当字符串输出:
- `cout << a1 << endl`会得到`C++`
- `printf("%s\n", a2)`会得到`C++`
- `puts(a2)`会得到`C++`,参数必须是字符数组。
- `cout << a2 + 1 << endl`会得到`++`
:::success
如果字符数组没有`\0`,不能按照字符串输出<br />输出碰到回车或空格,原样输出,直至遇到`\0`
:::
常用库函数:
| int strlen(char[] s) | 参数:字符数组<br />返回值:字符数组中存储的字符串的长度(不包括'\\0') | `cstring`头文件中 |
| --- | --- | --- |
| int strcmp(char[] a, char[] b) | 参数:两个字符数组<br />返回值:0, 1, -1分别表示a == b, a > b, a < b | `cstring`头文件中 |
| void strcpy(char[] b, char[] a) | 参数:两个字符数组<br />作用:将a复制给b | `cstring`头文件中 |
标准字符串:<br />可变长,可修改的字符序列,比字符数组更好用。需引入头文件`string`
初始化:
- `string s1`定义一个空字符串
- `string s2 = s1`,将`s1`拷贝到`s2`
- `string s3 = "zdkk"`,用字符串字面值定义
- `string s4(4, 'k')`定义一个字符串`s4 = "kkkk"`
- `string s5 ("12345", 1, 3)`定义一个字符串`s5 = "234"`(`1, 3`表示从下标1开始连续3个字符)
:::danger
string s = 'k' // 非法<br />string s;<br />s = 'k' // 合法,很神奇
:::
输入:<br />不能用`scanf()`<br />正确方式:`cin >> s`<br />`getline(cin, s)`表示读一行到字符串`s`中,**不会读取末尾的回车**。
输出:
- 可以用`printf`,需要调用函数。`printf("%s\n", s.c_str())`
`s.c_str()`返回字符串`s`的首字符地址。
- `puts(s.cstr())`
- `cout << s << endl`
常用函数
- `s.empty()`如果返回0,说明不为空。
- `s.length(), s.size()`返回字符串长度
- 字符串比较,直接用`==`即可,运算符重载
- 字符串拼接,`s1 += s2, s1.append(s2)`
- 遍历字符串,`for (auto &c : s)`
- 插入字符串,`s.insert(3, s2)`在下标3处插入s2
- 找子串,`s.substr(1, 3), s.substr(1)`,前者是从1开始的长度为3的子串,后者是从1开始的直至末尾的子串
- 删除某段子串,`s.erase(1, 3)`表示删除从下标1开始的长度为3的子串。
- 查看最后一个字符,`s.back()`
- 删除最后一个字符,`s.pop_back()`
- 字符串查找,`s.find(s2, 0)`从s串下标0的地方开始,查找s2的位置,如果不存在返回`string::npos`
- 字符串反转,`reverse(s.begin(), s.end())`
:::danger
字符串拼接必须保证有一方为字符串变量,另一方可以是字符串变量,字符串字面量或者字符<br />s += 'k' // 合法<br />s.append('k') // 非法,很神奇
:::
<a name="eJeG8"></a>
## 0x07 函数
C++的函数需要函数声明和函数定义<br />在函数内部可以使用静态变量`static int a = 0`,在多次调用时只会被初始化一次,等价于只能在函数内部使用的全局变量<br />函数参数可以有默认值`int foo(int a, int b = 10, int c = 15)`<br />数组作为参数传入,用`sizeof`测其长度,其实得到的结果是指针的大小<br />数组作为参数传入,除了第一维外,都需要明确指明大小
<a name="SEUuQ"></a>
## 0x08 类与结构体
类中的变量和函数被统一称为类的成员变量。<br />`private`后面的内容是私有成员变量,在类的外部不能访问;<br />`public`后面的内容是公有成员变量,在类的外部可以访问。
```cpp
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
class Person {
private:
int age, height;
double money;
string books[100];
public:
string name;
void say() {
cout << "I'm" << name << endl;
}
int get_age() {
return age;
}
int get_money() {
return money;
}
void add_money(double x) {
money += x;
}
};
int main() {
Person c;
c.name = "zdkk";
c.add_money(10000);
cout << c.get_age() << " " << c.get_money() << endl;
return 0;
}
类与结构体定义的唯一区别在于类中不写作用域,默认是private
,而结构体中默认为public
类中的变量类似于局部变量,也没有默认初始化。
// 类与结构体的构造函数与初始化
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
struct Person {
int age, height;
double money;
Person() {};
// Person(int _age, int _height, int _money) {
// age = _age;
// height = _height;
// money = _money;
// }
Person(int _age, int _height, int _money) : age(_age), height(_height), money(_money){}
};
int main() {
Person p;
Person c1(18, 180, 100);
Person c2 = {5, 100, 200};
Person c3 = Person(15, 150, 1000);
cout << c1.money << endl;
return 0;
}
0x09 指针和引用
指针指向存放变量的值的地址。因此我们可以通过指针来修改变量的值。
#include <iostream>
using namespace std;
int main() {
int a = 10;
int *p = &a, b = 20;
cout << p << endl;
}
数组名是一种特殊的指针,指向数组开始地址
#include <iostream>
using namespace std;
int main() {
char c;
int a[5] = {1, 2, 3, 4, 5};
cout << (void *)&c << endl;
cout << a << endl;
cout << (void*)&a[0] << endl;
cout << (void*)&a[1] << endl;
cout << (void*)&a[2] << endl;
cout << (void*)&a[3] << endl;
cout << (void*)&a[4] << endl;
}
指针支持的运算
#include <iostream>
using namespace std;
int main() {
char c;
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
cout << *p << endl;
cout << *(p + 1) << endl;
cout << *(a + 2) << endl;
cout << p[3] << endl;
cout << p[10] << endl; // 出大问题,这里居然不报越界,直接输出不确定值
}
引用和指针类似,相当于给变量起了个别名。
#include <iostream>
using namespace std;
int main() {
char c;
int a = 1;
int &p = a;
p = 10;
cout << a << endl;
}
链表
#include <iostream>
using namespace std;
struct Node {
int val;
Node* next;
Node(int _val) : val(_val), next(NULL) {}
};
int main() {
Node node = Node(1); // 这种写法返回的是Node对象
cout << node.val << endl;
Node *p = new Node(2); // 这种写法返回的是Node对象的引用
cout << p->val << endl;
}