字符串
void CStyleStringType() {
char str[] = "Hello";
// 打印字符串长度
std::cout << "str length: " << ::strlen(str) << std::endl;
for (size_t i = 0; i < strlen(str); ++i) {
std::cout << "index " << i << " value: " <<str[i] << std::endl;
}
char a[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
std::cout << "a length: " << ::strlen(a) << std::endl;
for (size_t i = 0; i < strlen(a); ++i) {
std::cout << "index " << i << " value: " <<a[i] << std::endl;
}
char b[13]{};
::strcat(b, a); // 字符串拼接
for (size_t i = 0; i < strlen(b); ++i) {
std::cout << "index " << i << " value: " <<b[i] << std::endl;
}
// 字符串内容比较 (str < a)-1,(str == a)0,(str > a)1
std::cout << strcmp(str, a) << std::endl;
}
输出结果:
str length: 5
index 0 value: H
index 1 value: e
index 2 value: l
index 3 value: l
index 4 value: o
a length: 5
index 0 value: H
index 1 value: e
index 2 value: l
index 3 value: l
index 4 value: o
index 0 value: H
index 1 value: e
index 2 value: l
index 3 value: l
index 4 value: o
0
- c风格字符串,以 char arr[6] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’} 形式存储,注意末尾一定需要有’\0’
- c提供头文件 #include 对字符串进行相关操作
- c风格字符串中,‘\0’ 只是作为字符串的结尾,并不算在字符串长度中,但数组中必须有 ‘\0’ 的存储位置
void StingType() {
std::string a = "Hello"; // 直接初始化
std::cout << "a length: " << a.size() << std::endl;
std::cout << "a value: " << a << std::endl;
std::string b(a); // 一个字符串初始化另一个字符串
std::cout << "b value: " << b << std::endl;
b = b + b; // 拼接
std::cout << "b + b value: " << b << std::endl;
if (a == b) {
std::cout << "a == b" << std::endl;
} else {
std::cout << "a != b" << std::endl; // a 不等于 b
}
std::string c = "\\\\"; // 必须转义才能赋值 \ ;
std::cout << "c value: " << c << std::endl;
std::string d = R"(\\)"; // 原始字符,忽略转义
std::cout << "d value: " << d << std::endl;
// 必须带前缀才能初始化
wchar_t e[] = L"Hello"; // 宽字符
char16_t f[] = u"Hello"; // 对应 utf16 编码字符
char32_t g[] = U"Hello"; // 对应 utf32 编码字符
std::wcout << "e value: " << e << std::endl;
// char16_t、char32_t 不能直接打印,需要转换
std::cout << "f value: " << f << std::endl;
std::cout << "g value: " << g << std::endl;
}
输出结果:
a length: 5
a value: Hello
b value: Hello
b + b value: HelloHello
a != b
c value: \
d value: \
e value: Hello
f value: 0x65fca8
g value: 0x65fc90
- c++提供专门处理字符串的类 std::string 代替c风格的字符串处理,头文件为 #include < string >
- 可以使用c风格字符串来初始化 string 对象
- c++11可以使用初始化列表对其进行初始化
- string 类重载了 =、+、==运算符,所以赋值、拼接、比较等操作更容易
- string 还有多种的字符串字面值,c++11提供了 u8 和原始字符串 R 修饰字面值
- 有关 string 类的操作函数有许多,也可通过STL的迭代器进行操作,先初步认识 string 类
结构体
void StructType() {
struct Student {
int id;
std::string name; // 非 POD 类型
};
struct Student a; // c
Student b; // c++
a = {1, "a"};
b.id = 2;
b.name = "b";
Student c = {3, "ccc"};
Student d{4, "d"};
struct Teacter { // 默认初始化值
int id = 0; // 成员变量
std::string name = "teacter"; // 非 POD 类型
void Print() { // 成员函数
std::cout << "id:" << id << " name: " << name << std::endl;
}
Teacter(){}
Teacter(int i, const std::string &n) {
id = i;
name = n;
}
};
// 报错, 添加成员函数时,成员变量需要初始化,Teacter结构体没有两个参数的构造函数
// int id = 0;
// Teacter e = {20000, "Mr.Li"};
Teacter f; // 默认编译器生成无参构造函数
f.id = 1000;
f.name = "Mr.Li";
f.Print(); // 调用成员函数
// 添加 Teacter(int i, const std::string &n) {...}构造函数
Teacter g{20000, "Mr.Li"}; // OK
Teacter h = {3000, "Mr.Li"}; // OK
// 因为 f 初始化时是编译器默认生成的无参构造函数,但添加有参构造函数时,
// 编译时有构造函数编译器就不会自动生成无参构造函数,需要显式添加无参构造函数 Teacter(){},类似 f 这样的变量声明才能编译通过
struct Bit {
// int,4 * 8 = 32 bit
int low : 10;
int : 10; // 占位,未使用
int : 10;
};
}
输出结果:
id:1000 name: Mr.Li
- c++对c的结构体(struct)进行了功能拓展,基本上等同于类(class)的功能
- 区别就是 struct 成员默认访问权限是 public ,class 成员默认访问权限是 private
- 结构体之间可以嵌套,c++声明结构体类型变量时可以不加 struct 关键字
- 初始化是按照声明的变量类型进行初始化(隐式转换亦可以),c++11的初始化列表({})也可以对结构体初始换
- 使用初始化列表需要提供对应的构造函数
- 结构体中出现这样直接给成员变量赋值的语句
int id = 0;
时,都需要提供对应的构造函数才能使用初始化列表 - 结构体类型也可定义结构体数组变量,
Teacher a[10];
- 与c语言一样,c++也允许指定占用特定位数的结构体成员变量
共用体
void UnionType() {
union IntConvert {
int a;
char arr[4];
};
IntConvert u;
u.a = 21585461;
std::cout <<"a value: " << u.a << std::endl;
std::cout <<"arr[0] value: " << (int)u.arr[0] << std::endl;
std::cout <<"arr[1] value: " << (int)u.arr[1] << std::endl;
std::cout <<"arr[2] value: " << (int)u.arr[2] << std::endl;
std::cout <<"arr[3] value: " << (int)u.arr[3] << std::endl;
union TestUnion {
short a;
double b;
int c;
};
std::cout <<"union length: " << sizeof (TestUnion) << std::endl; // double 的长度
}
输出结果:
a value: 21585461
arr[0] value: 53
arr[1] value: 94
arr[2] value: 73
arr[3] value: 1
union length: 8
- 存储不同的数据类型,但只能同时存储其中的一种类型,长度与存储类型中最大长度的类型长度一样
- 匿名共用体即没有名称的共用体
枚举
void EnumType() {
// 弱枚举类型
enum Sq {
E1,
E2
};
std::cout << "E1 value: " << E1 << std::endl; // 不使用作用域打印
std::cout << "E2 value: " << E2 << std::endl;
enum Sq2 {
S1 = 10,
S2 = 12
};
std::cout << "S1 value: " << Sq2::S1 << std::endl; // 使用作用域打印
std::cout << "S2 value: " << Sq2::S2 << std::endl;
// 强类型枚举
enum class Sq3 {
N1 = 10,
N2 = 12
};
// 打印时必须强制转换为 int 类型
std::cout << "N1 value: " << (int)Sq3::N1 << std::endl; // 必须使用作用域打印
std::cout << "N2 value: " << (int)Sq3::N2 << std::endl;
}
输出结果:
E1 value: 0
E2 value: 1
S1 value: 10
S2 value: 12
N1 value: 10
N2 value: 12
- c++提供了另一创建符号常量的方式,使用枚举方式,成员符号常量以逗号分隔
- 枚举默认可以和整型相互转换,c++11提供了一种强类型枚举方式,不能和整数相互转换
- 枚举默认第一个符号常量的整数值为 0,下一个在上一个数值的基础上默认加1
- 枚举中符号常量对应的可以自指定,注意同一个枚举中的符号常量对应的整数不能有重复
- 枚举也可以没有名字,即匿名枚举
指针
void PointerType() {
// 声明指针
int *p; // 未初始化,指针指向内容未知
char *p2;
// 初始化
int a = 10;
int *p3 = &a; // 指向变量 a 的地址
int *p4 = nullptr; // 初始化为 nullptr (未指向有效地址)
int *p5 = p3; // 指向 p3 所指向的地址
// 对指针解引用
std::cout << "p3 value: " << &p3 << std::endl; // 输出p3自己的地址
std::cout << "p3 value: " << p3 << std::endl; // 输出p3指向的地址
std::cout << "a value: " << &a << std::endl; // 输出 a 的地址
std::cout << "p3 value: " << *p3 << std::endl; // 解引用,输出p3指向的地址的值
// 通过指针修改指向变量的值
*p3 = 12;
std::cout << "a value: " << a << std::endl; // 输出 a 的地址
// 指针和数组
int arr[10] = {1, 2, 3, 4, 5};
for (int i = 0; i < 10; ++i) // 输出数组
std::cout << arr[i] << "\t";
std::cout << std::endl;
std::cout << "total array length: " << sizeof arr << std::endl; // 数组总长度
std::cout << "array size: " << sizeof arr / sizeof (int) << std::endl; // 数组元素数量
int *p6 = arr; // 数组名就是数组首地址
for (int i = 0; i < 10; ++i) // 通过指针操作输出数组
std::cout << p6[i] << "\t";
std::cout << std::endl;
// 指针运算
std::cout << "p6 address value: " << p6 << std::endl;
for (int i = 0; i < 10; ++i) // 通过指针操作输出数组
std::cout << *(p6 + i) << "\t";// 注意p6指向的地址并未发生变化
std::cout << std::endl;
std::cout << "p6 address value: " << p6 << std::endl;
for (int i = 0; i < 10; ++i) // 通过指针操作输出数组
std::cout << *p6++ << "\t"; // 注意p6指向的地址发生了变化,最终指向数组的最后一个元素的地址
std::cout << std::endl;
std::cout << "p6 address value: " << p6 << std::endl;
std::cout << "arr last member var address value: " << &arr[10] << std::endl;
std::cout << "p6 address value: " << --p6 << std::endl;
std::cout << "arr last member var address value: " << &arr[9] << std::endl;
// 静态联编 & 动态联编
// 创建数组时,其大小就已经确定好了,采用静态联编(编译期间已经确定数组分配多少空间)
// 使用指针创建数组时(动态数组),采用动态联编(运行时才动态分配空间)
// 指针创建数组
int *p7 = new int[10]; // 动态创建十个元素的 int 型数组,未初始化,随机值
for (int i = 0; i < 10; ++i) // 通过指针操作输出数组
std::cout << *(p7 + i) << "\t";// 注意p6指向的地址并未发生变化
std::cout << std::endl;
int *p8 = new int[10]{0}; // 动态创建十个元素的 int 型数组,初始化为零
for (int i = 0; i < 10; ++i) // 通过指针操作输出数组
std::cout << *(p8 + i) << "\t";// 注意p6指向的地址并未发生变化
std::cout << std::endl;
// c++需要手动管理程序内存等使用资源,处于栈空间的变量由系统自动释放,处于堆内存的空间需要手动管理
// new & delete,new 关键字申请在堆内存中开辟空间,失败会抛出异常,所以需要手动释放,释放使用 delete 关键字
int *p9 = new int[10]{0};
delete [] p9; // new type[] 形式申请的数组,必须匹配 delete [] 形式,否则出现内存泄漏
p9 = nullptr; // 指向不可用空间
int *p10 = new int;
delete p10;
p10 = nullptr;
}
输出结果:
p3 value: 0x65fd28
p3 value: 0x65fd34
a value: 0x65fd34
p3 value: 10
a value: 12
1 2 3 4 5 0 0 0 0 0
total array length: 40
array size: 10
1 2 3 4 5 0 0 0 0 0
p6 address value: 0x65fd00
1 2 3 4 5 0 0 0 0 0
p6 address value: 0x65fd00
1 2 3 4 5 0 0 0 0 0
p6 address value: 0x65fd28
arr last member var address value: 0x65fd28
p6 address value: 0x65fd24
arr last member var address value: 0x65fd24
14352720 0 14352720 0 14352720 0 14352720 0 24 0
0 0 0 0 0 0 0 0 0 0
- 指针是存储的是地址,而不是存储的值
- new 关键字在堆上申请内存,需要手动管理,释放时使用 delete 关键字释放,注意匹配 new & delete,new [] & delete []
内存管理
int e = 10; // 全局变量,全局/静态存储区
const int s = 10; // 常量存储区
void NewDelete() {
// 局部变量,自动存储区(栈)
int a = 10;
// 静态变量,全局/静态存储区
static int b = 10;
// 动态变量,动态存储区(自由存储区或堆)
int *p = new int(10);
delete p;
p = nullptr;
// 线程变量, 只在当前线程可用,线程存储区
thread_local int t = 10;
}
- 不同存储区对变量在程序中的生命周期有不同管理
- 自动存储:函数内部定义的常规变量使用自动存储空间,自动创建,自动释放,无需手动管理,通常存储在栈(LIFO)中
- 全局/静态存储:整个程序执行期间一直存在,static 关键字修饰的变量或定义在最外边的变量
- 动态存储:使用 new 在堆上(或称为自由存储区)申请内存空间的变量,内存需要手动释放
- 常量存储:存放的是常量,不允许修改
- 线程存储:线程局部变量,c++11新添加的类型
循环语句
void ForCirculation() { // for 循环
char a[] = "HelloWorld";
int size = strlen(a);
// for循环执行顺序:1. i = 0; 2. 判断 i < size; 3. 执行循环体std::cout << a[i] << "\t"; 4. ++i;
// 5. 继续执行 2 步骤; 6. 继续执行 3 步骤; 7. 继续执行 4 步骤; ...
for (int i = 0; i < size; ++i) { // 标准 for 语法
std::cout << a[i] << "\t";
}
std::cout << std::endl;
int i = 0;
for (; i < size; ++i) { // 变体 1
std::cout << a[i] << "\t";
}
std::cout << std::endl;
int n = 0;
for (; n < size;) { // 变体 2
std::cout << a[n] << "\t";
++n;
}
std::cout << std::endl;
int s = 0;
for (;;) { // 变体 3 死循环
if (s == size) // 满足条件跳出死循环
break;
std::cout << a[s++] << "\t";
}
std::cout << std::endl;
}
输出结果:
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d
H e l l o W o r l d
void WhileCirculation() {
char a[] = "HelloWorld";
int size = strlen(a);
int i = 0;
// 判断条件是 bool 值,未知循环次数,满足条件即可退出
while (i < size) { // 类似 for 循环
std::cout << a[i++] << "\t";
}
std::cout << std::endl;
i = 0;
while (true) { // 死循环
std::cout << a[i] << "\t";
if (a[i++] == 'd') // 判断满足条件跳出死循环
break;
}
std::cout << std::endl;
}
输出结果:
H e l l o W o r l d
H e l l o W o r l d
void DoWhileCirculation() {
char a[] = "HelloWorld";
int size = strlen(a);
int i = 0;
// 先执行循环体,在进行条件判断
do {
std::cout << a[i++] << "\t";
} while (i < size);
std::cout << std::endl;
i = 0;
do {
std::cout << a[i++] << "\t"; // 改变i值,i = i + 1;
} while (i == 0); // 不满足条件,不继续执行循环体,循环结束
std::cout << std::endl;
}
输出结果:
H e l l o W o r l d
H
void CirculationControl() {
char a[] = "HelloWorld";
int size = strlen(a);
for (int i = 0; i < size; ++i) {
std::cout << a[i] << "\t";
if (i == 4) // 输出前五个字符
break; // 满足条件直接跳出循环
}
std::cout << std::endl;
for (int i = 0; i < size; ++i) {
if (i < 5) // 输出除去前五个字符的内容
continue; // 满足条件跳到循环开始继续执行下一轮循环体,不在执行后面的循环体内容
std::cout << a[i] << "\t";
}
std::cout << std::endl;
for (int i = 0; i < size; ++i) {
std::cout << a[i] << "\t";
if (i == 4) // 输出前五个字符
return; // 满足条件直接返回,整个函数执行结束
}
}
输出结果:
H e l l o
W o r l d
H e l l o
- 需要程序重复执行的任务可使用循环实现
- 循环有三种方式,for循环、while循环和do while循环
- 循环语句使用时注意不可出现死循环
- 分支或循环控制关键字,break、continue和return