课件
7.1 引子
引子
notes
简述
了解 C++ 和 C 之间的差异,知道 C++ 是完全兼容 C 的,掌握好 C 之后学习 C++ 将轻松很多。
C 和 C++ 的区别
C 和 C++ 是两种不同的编程语言,它们各有优缺点,在不同的应用场景中都有自己的优势
- 编程范式:
- C 是过程化编程语言
- C++ 是面向对象编程语言
- C++ 中引入了类、继承、多态等概念,可以更方便地进行对象封装和抽象
- 编译器:
- C++ 编译器支持 C++ 标准库和 STL,而 C 编译器不支持
- C++ 编译器也可以编译C代码,但是 C 编译器不能编译 C++ 代码
- 关键字:
- C 和 C++ 中有不同的关键字和语法
- 例:C++ 中的关键字包括 virtual、friend、namespace 等,而 C 中则没有这些关键字
- 应用领域:
- C++ 具有更多的功能和抽象能力,因此它更适合于大型项目和应用程序的开发
- C 更适合于系统级编程和嵌入式系统开发
- 内存管理:
- C++ 具有更高级别的内存管理,例如引入了构造函数和析构函数的概念,可以更方便地进行内存管理
- 在 C 中,程序员需要手动进行内存管理,例如使用 malloc 和 free 函数
- 其他特性:
- C++ 中还引入了异常处理、模板等特性,这些特性在 C 中没有
- C++ 还具有函数重载、运算符重载等特性,可以更方便地进行代码重用和编程
7.2 初窥输入输出
初窥输入输出
notes
简述
体验 C 和 C++ 在输入输出上的差异
#include
#include <iostream>
是 C++ 中用于输入输出的标准库头文件- 标准库 iostream 中定义了以下 IO 对象:
- cin:标准输入流(istream 对象),用于从标准输入设备(通常是键盘)读取数据
- cout:标准输出流(ostream 对象),用于向标准输出设备(通常是显示器)写入数据
- cerr:标准错误流,用于向标准错误设备写入错误消息
- clog:标准日志流,用于向标准错误设备写入程序运行时的消息(与 cerr 类似,但 clog 不会影响程序的返回值)
- cin、cout、cerr、clog 这些 系统预定义流对象 都是 静态对象,因此可以直接使用,不需要进行初始化
静态对象
- 静态对象是在程序的整个生命周期中都存在的对象
- 静态对象一般在类的静态成员中定义
- 静态对象不需要初始化,因为它们在编译时已经被初始化
>> 和 << 操作符
在 C++ 中,>> 和 << 是位运算符和位移运算符,也是流输入输出运算符。
作为流输入输出运算符,它们的功能是:
- << 运算符:将数据输出到流中,通常用于控制台输出
运算符:用于从流中读取数据,并将读取到的数据赋值到对应的变量中、内存区中
std 命名空间
- 在 C++ 中,std 是一个命名空间(namespace),包含了标准 C++ 库中所有的标识符(常用的数据类型、函数、流对象、容器等等)
- 使用 std 命名空间的好处是 可以避免命名冲突,确保代码的可读性和可维护性。
- cin、cout、cerr、clog 都定义在命名空间 std 中,因此使用时需要加上命名空间限定符,如 std::cout
- 在使用标准输入输出流对象 cin 和 cout 时,可以在程序开头加上
using namespace std;
引用 std 命名空间,这样我们就可以直接使用 std 命名空间中的函数和变量了
流
- 流是从某种输入输出(IO)设备上读入或写出的字符序列
- 在 C++ 中,流有两种类型:输入流和输出流
- 输入流用于从输入设备(如键盘或文件)读取字符
- 输出流用于向输出设备(如控制台或文件)写入字符
- 流提供了一种可移植的方法,用于处理不同类型的输入和输出设备
- C++ 标准库提供了一个流类库
iostream
,其中包括输入/输出流类、文件流类、字符串流类等,使用这些类可以很方便地进行输入输出操作
不同数据类型使用一条语句输出
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 10;
double b = 3.14;
string c = "Hello, world!";
cout << a << ", " << b << ", " << c << endl;
return 0;
}
/* 运行结果:
10, 3.14, Hello, world!
*/
#include <iostream>
#include <string>
using namespace std;
int main() {
int a = 10;
double b = 3.14;
string c = "Hello, world!";
// cout << a << ", " << b << ", " << c << endl;
cout << a;
cout << ", ";
cout << b;
cout << ", ";
cout << c;
cout << endl;
return 0;
}
/* 运行结果:
10, 3.14, Hello, world!
*/
两种方式都可以输出相同的结果,只是表现形式不同。
using namespace std;
- 这是 C++ 的一个命名空间的声明语句,它的作用是指定当前文件中使用 std 命名空间中的所有名称,这样就可以直接使用 std 命名空间中的所有函数和对象,而无需在每个名称前面都添加 std:: 的限定符了
- 如果没有这条语句,那么在使用 std 命名空间中的名称时就需要加上限定符 std::,cout 就没法直接用,应该写成
std::cout
两数求和 | 分别使用 C、C++ 的输入、输出写发来实现
#include <stdio.h>
int main() {
int a, b, sum;
printf("请输入两个整数:\n");
scanf("%d%d", &a, &b);
sum = a + b;
printf("它们的和是:%d\n", sum);
return 0;
}
/* 运行结果:
请输入两个整数:
1 2
它们的和是:3
*/
#include <iostream>
using namespace std;
int main() {
int a, b, sum;
cout << "请输入两个整数:" << endl;
cin >> a >> b;
sum = a + b;
cout << "它们的和是:" << sum << endl;
return 0;
}
/* 运行结果:
请输入两个整数:
1 2
它们的和是:3
*/
两者的主要差异在于:
- 输入输出的函数不同,C 语言使用的是 scanf 和 printf,而 C++ 使用的是 cin 和 cout
- C++ 在输入、输出时,无需去写格式控制字符,而 C 语言需要
使用 C++ 的 cin 实现连续输入多个数据
#include <iostream>
using namespace std;
int main() {
int a, b, c;
cout << "请输入三个整数,用空格分隔:" << endl;
cin >> a >> b >> c;
cout << "输入的三个整数分别为:" << a << ", " << b << ", " << c << endl;
return 0;
}
/* 运行结果:
请输入三个整数,用空格分隔:
12 23 34
输入的三个整数分别为:12, 23, 34 */
- 在 C++ 中,<< 是一种二元运算符,是输入运算符,表示向流中插入数据的操作符
- 当 cin 从输入流中提取数据时,它会自动跳过空格和换行符等空白字符,直到读取到一个有效字符为止
- 如果输入的数据类型与期望的数据类型不匹配,会产生输入错误,需要进行错误处理
源码
#include <iostream>
using namespace std;
int main() {
int a;
char buf[32];
cin >> a >> buf;
cout << a << " " << buf << endl;
return 0;
}
/*
5 cheng du
5 cheng */
输入运算符会按照空格分割输入,将 5 存储在 a 中,”cheng” 存储在 buf 中,而 “du” 则被留在输入缓冲区中等待下一次读入。
如果想要让 cheng du 存入到 buf 中,该这么改写
#include <iostream>
using namespace std;
int main() {
int a;
char buf[32];
// cin >> a >> buf;
cin >> a;
cin.ignore();
cin.getline(buf, 32);
cout << a << " " << buf << endl;
return 0;
}
/* 运行结果:
5 cheng du
5 cheng du */
其中 cin.ignore() 是为了清除输入缓冲区中的换行符,防止影响后面的 getline() 函数
编程实现用 C++ 的输入流运算符输入 10 个数进行冒泡排序
#include <iostream>
using namespace std;
int main() {
int arr[10];
cout << "请输入 10 个数字:\n";
for (int i = 0; i < 10; i++) {
cin >> arr[i];
}
// 冒泡排序
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10 - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
cout << "升序排序后的结果:\n";
for (int i = 0; i < 10; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
/* 运行结果:
请输入 10 个数字:
23 34 25 98 20 40 50 97 88 94
升序排序后的结果:
20 23 25 34 40 50 88 94 97 98 */
7.3 数据类型与表达式
数据类型与表达式
notes
简述
看懂课件中的源码,掌握 new、delete 的用法,独立完成结尾的练习题。
C++ 相较于 C 新增的数据类型
低类型、高类型
高到低没问题,低到高可能会导致精度丢失
思考:C++ 中新增一个 bool 类型的目的
C++ 新增 bool 类型的目的是为了提高代码的可读性和可维护性。
在 C++ 之前,程序员通常使用 int 类型来表示布尔值。使用 int 类型存在以下问题:
- 内存占用过大:int 类型通常占用 4 个字节,而布尔值只需要占用 1 个字节的空间,因此使用 int 类型会造成浪费。
- 可读性差:使用 int 类型表示布尔值会造成代码可读性差的问题。代码中出现大量的 0 和 1,不容易理解。
- 可维护性差:使用 int 类型表示布尔值,容易在代码中出现错误。例如,将布尔值赋值为 2,因为在 C 语言中,任何非零的值都被视为 true,这会导致代码出现错误。
使用 bool 类型可以解决上述问题,提高代码的可读性和可维护性。
#include <iostream>
using namespace std;
int main() {
cout << "Size of int: " << sizeof(int) << endl;
cout << "Size of bool: " << sizeof(bool) << endl;
return 0;
}
/* 运行结果:
Size of int: 4
Size of bool: 1 */
#include <iostream>
int main() {
bool is_true = true;
bool is_false = false;
std::cout << std::boolalpha; // 打印 true 或 false,而不是 1 或 0
std::cout << "is_true = " << is_true << std::endl;
std::cout << "is_false = " << is_false << std::endl;
int num1 = 10, num2 = 20;
bool result = num1 < num2;
std::cout << "num1 < num2 is " << result << std::endl;
return 0;
}
/*
is_true = true
is_false = false
num1 < num2 is true */
C++ 中引入了内置的 bool 类型,以及对应的 true 和 false 关键字,这是 C 语言所没有的。
- C 语言中通常使用整型(如 int)来表示布尔类型,0 表示 false,非 0 表示 true
- 而 C++ 中的 bool 类型只有两个值,true 和 false,并且在 C++ 中使用 bool 更为安全和直观
7.4 动态内存管理
动态内存管理
notes
简述
介绍了如何在 C++ 中使用 new、delete 关键字来实现内存管理。
动态内存管理 new、delete
C++ 中有多种方式进行动态内存管理,其中最常用的是 new 和 delete 关键字
- 使用 new 可以在堆中动态分配内存
- 使用 delete 可以释放已分配的内存
可以在使用 new 运算符在分配空间时,同时进行初始化操作,初始值可以是:
- 常量
- 变量
- 表达式
#include <iostream>
int main() {
// 动态分配整型空间
int *p = new int(123);
// 输出动态分配的整型
std::cout << "The value of *p is " << *p << std::endl;
// 释放动态分配的空间
delete p;
return 0;
}
/* 运行结果:
The value of *p is 123 */
int *p = new int(123);
new int;
分配一块整型空间new int(123);
初始化这块整型数据存储区的值为 123- 定义一个整型指针 p 指向这块空间
#include <iostream>
int main() {
// 动态分配一个 int 类型的数组,包含 5 个元素
int *arr = new int[5];
// 为数组元素赋值
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
// 打印数组元素
for (int i = 0; i < 5; i++) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}
// 释放数组内存
delete[] arr;
return 0;
}
/*
arr[0] = 1
arr[1] = 2
arr[2] = 3
arr[3] = 4
arr[4] = 5 */
动态分配一维数组的存储空间
#include <iostream>
using namespace std;
int main() {
int n;
cout << "请输入数组的长度:";
cin >> n;
int* p = new int[n]; // 动态分配 n 个 int 类型的空间
// 从键盘输入 n 个数,并赋值给数组
for (int i = 0; i < n; i++) {
cout << "请输入第 " << i + 1 << " 个数:";
cin >> p[i];
}
// 输出数组中的数
cout << "数组中的数是:";
for (int i = 0; i < n; i++) {
cout << p[i] << " ";
}
cout << endl;
// 释放空间
delete[] p;
return 0;
}
/* 运行结果:
请输入数组的长度:3
请输入第 1 个数:11
请输入第 2 个数:22
请输入第 3 个数:33
数组中的数是:11 22 33 */
#include <iostream>
using namespace std;
int main() {
int i, *p;
p = new int[100];
if (p == NULL) {
cout << "Allocation failure!\n";
} else {
for (i = 0; i < 100; i++)
p[i] = i + 1;
for (i = 0; i < 100; i++)
cout << p[i] << ' ';
delete[] p;
}
}
/* 运行结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 */
源码
#include <iostream>
using namespace std;
int main() {
int* p;
p = new int;
if (p == NULL) {
cout << "Allocation failure!\n";
} else {
*p = 15;
cout << *p; // => 15
delete p;
}
return 0;
}
#include <iostream>
using namespace std;
int main() {
int* p;
p = new int(100);
if (p == NULL) {
cout << "Allocation failure!\n";
} else {
cout << *p; // => 100
delete p;
}
return 0;
}
#include <iostream>
using namespace std;
int main() {
int* p;
p = new int(98.5);
if (p == NULL) {
cout << "Allocation failure!\n";
} else {
cout << *p; // => 98
delete p;
}
return 0;
}
/* 运行结果:
/workspace/0069_23.03.09/main.cpp:5:17: warning: implicit conversion from 'double' to 'int' changes value from 98.5 to 98 [-Wliteral-conversion]
p = new int(98.5);
~~~ ^~~~
1 warning generated.
98 */
warning: implicit conversion from 'double' to 'int' changes value from 98.5 to 98 [-Wliteral-conversion]
这是一个警告信息,提示程序中将一个 double 类型的值 98.5 隐式转换为了 int 类型的值 98。这可能会导致精度的损失或程序的错误行为。建议在创建 int 类型的对象时,直接使用整型的值,而不是将浮点型隐式转换为整型。可以将代码改为:
int *p;
p = new int(98);
练习题
请编程实现如下功能:
- 用 C++ 动态内存分配分配 n 个整数空间
- 用随机数初始化这 n 个空间,随机数范围为
[0, n - 1]
- 输出这 n 个空间的最大和最小数值
- 释放 n 个空间
#include <iostream>
#include <cstdlib> // 包含随机数生成函数 srand、rand
#include <ctime> // 包含时间函数 time
using namespace std;
int main() {
int n;
cout << "请输入要分配的整数空间大小:";
cin >> n;
// 分配内存空间
int* arr = new int[n];
// 用随机数初始化
srand(time(NULL));
for (int i = 0; i < n; i++) {
arr[i] = rand() % n;
}
// 计算最大值和最小值
int max_val = arr[0], min_val = arr[0];
for (int i = 1; i < n; i++) {
if (arr[i] > max_val) {
max_val = arr[i];
}
if (arr[i] < min_val) {
min_val = arr[i];
}
}
// 输出结果
cout << "生成的 " << n << " 个随机数为:";
for (int i = 0; i < n; i++) {
cout << arr[i] << ' ';
}
cout << endl;
cout << "最大值为:" << max_val << endl;
cout << "最小值为:" << min_val << endl;
// 释放内存空间
delete[] arr;
return 0;
}
/* 运行结果:
请输入要分配的整数空间大小:10
生成的 10 个随机数为:2 2 4 2 8 3 7 4 1 7
最大值为:8
最小值为:1 */
7.5 函数重载
函数重载
notes
简述
介绍了函数重载和函数形参默认值。
函数重载
- 函数重载是指在同一作用域内定义的多个函数,它们 具有相同的函数名,但参数列表不同(包括参数个数、类型和顺序)
- 函数重载可以 让编译器能够根据传递给函数的参数的类型及个数来判断应该调用哪个函数
- 不能以形参名字或函数返回类型的不同来区分函数
- 不要将不同功能的函数定义为重载函数,以免出现混淆
- 通过函数重载,我们可以 在不改变函数名称的情况下实现函数功能的多样性,从而使代码更加简洁、易于维护和阅读
- 函数重载是一种强大的编程工具,它为程序员提供了更加灵活的编程方式
#include <iostream>
using namespace std;
void print(int num) {
cout << "Printing an integer: " << num << endl;
}
void print(double num) {
cout << "Printing a double: " << num << endl;
}
int main() {
int a = 5;
double b = 3.14;
print(a); // 调用 print(int)
print(b); // 调用 print(double)
return 0;
}
/*
Printing an integer: 5
Printing a double: 3.14
*/
void print(int num)
、void print(double num)
- 我们定义了两个名为 print 的函数,一个接收 int 类型参数,另一个接收 double 类型参数,这两个函数的函数名相同,但是它们的参数类型不同,这就是函数重载
- 在程序中,我们分别调用了这两个函数,并且根据参数类型的不同,自动选择了正确的函数进行调用,这种功能可以 让程序员更方便地使用函数,而不用为了区分不同的功能而修改函数名
形参默认值
#include <iostream>
using namespace std;
int add(int a, int b = 10) {
return a + b;
}
int main() {
int a = 5;
int b = 7;
cout << "a + b = " << add(a, b) << endl; // 输出 "a + b = 12"
cout << "a + 10 = " << add(a) << endl; // 输出 "a + 10 = 15"
return 0;
}
课件源码
#include <iostream>
using namespace std;
int add(int x = 5, int y = 6);
void func();
int main() {
int add(int x = 7, int y = 8);
int ret = add(); // 实现 7 + 8
func();
cout << "main " << ret << endl;
return 0;
}
void func() {
int ret = add(); // 实现 5 + 6
cout << "func " << ret << endl;
}
int add(int x, int y) { return x + y; }
/* 运行结果:
func 11
main 15
*/
#include <iostream>
using namespace std;
int add(int x, int y) { return x + y; } // 第一个 add
int add(int x, int y, int z = 2) { return x + y + z; } // 第二个 add
int main() {
int ret = add(10, 20); // => Call to 'add' is ambiguousclang(ovl_ambiguous_call)
cout << "main " << ret << endl;
return 0;
}
add(10, 20)
调用 add 函数时出现了二义性
调用第一个 add 可以这么写
由于第二个 add 的最后一个参数带有默认值,所以调用第二个 add 是可以省略第三个参数的,因此,调用第二个 add 也可以这么写
7.6 内联函数
内联函数
notes
简述
内联函数中的细节蛮多的,笔记记录的也比较乱,很多知识盲区,后续学到了之后再完善笔记,并添加相应的 demo 来辅助理解。
宏定义的利与弊
- 宏的好处是没有类似于普通函数调用时的系统开销,并且宏定义的参数可以适宜大多数类型的数据
- 宏定义有时会产生不可预料的副作用
#include <iostream>
#define abs(a) ((a) < 0 ? -(a) : (a))
using namespace std;
int main() {
int m = -2, ret = abs(++m); // ret = ((++m) < 0 ? -(++m) : (++m));
cout << "m = " << m << endl;
cout << "ret = " << ret << endl;
return 0;
}
/* 运行结果:
m = 0
ret = 0
*/
宏定义中的 ++m 会被展开两次,上述代码中的 ret = abs(++m);
写法等效于 ret = ((++m) < 0 ? -(++m) : (++m));
这种写法。因此,最终 m、ret 的结果都是 0。
内联函数
- 在 C++ 中,inline 是用来修饰函数的关键字。使用 inline 关键字可以将函数的定义体直接插入到函数被调用的地方,从而消除函数调用的开销。这样的函数被称为内联函数。
- C++ 中的内联函数既具有宏定义的优点,又客服了宏定义的缺点
- 在函数名前边加上
inline
即为内联函数,如:inline void func(int a, int b);
- 在编译时,会在调用 func 的地方用函数体进行替换,因此,程序执行时会减少调用开销,从而提高程序的执行效率
- 并非所有函数都需要定义为内联函数,一般只会将那些频繁被调用的,并且函数体较小的(只有几条语句) 函数定义为内联函数
- 内联函数内不允许有循环语句和 switch 语句,否则按照普通函数来处理
- inline 关键字仅仅是对编译器的建议,编译器是否将函数作为内联函数实现取决于编译器本身和函数的复杂程度等因素。
- 使用 inline 关键字定义函数时,一般需要将函数的定义体直接放在头文件中,以便在多个源文件中都可以使用该函数。否则,在链接时可能会出现函数重复定义的错误。
- 在可读性、可维护性、可移植性等方面,内联函数都比宏定义函数更好
- 宏定义函数是在预处理阶段进行简单的文本替换,它没有函数调用的开销,但也没有函数的类型检查和参数检查。它只是一种简单的文本替换,无法保证程序的健壮性和可读性。
- 内联函数则是在编译阶段进行内联展开,它可以像宏定义函数一样避免函数调用的开销,但它是编译器在编译阶段进行的优化,具有函数的类型检查和参数检查,而且也不会引入宏定义函数的问题。同时,内联函数可以被放在头文件中,使得代码更加清晰、简洁。
- 宏定义函数在某些特定场合下仍然有用,但一般情况下,建议使用内联函数
- 缺点:
- 内联函数会增加代码的体积,因为每次调用内联函数都需要将其代码复制到调用处,所以过度使用内联函数可能会导致可执行程序的大小增加
- 内联函数的可重用性和扩展性较差,因为内联函数的代码无法被其他函数调用,也就无法在其他函数中重用
- 内联函数的代码也无法被动态链接库使用,因为它的代码是被直接嵌入到调用它的函数中的,无法在动态链接库中找到独立的代码段
#include <iostream>
using namespace std;
inline int abs(int a) { return a < 0 ? -a : a; }
int main() {
int m = -2, ret = abs(++m);
cout << "m = " << m << endl;
cout << "ret = " << ret << endl;
return 0;
}
/* 运行结果:
m = -1
ret = 1
*/
#include <iostream>
inline int add(int x, int y) {
return x + y;
}
int main() {
int a = 3, b = 4;
std::cout << add(a, b) << std::endl; // => 7
return 0;
}
函数调用的开销
在编写代码时,应该尽量减少函数的调用次数,尽可能地将重复的代码放在一个函数中,以避免不必要的开销。
- 函数调用的开销主要是指将控制权从主调函数转移到被调函数时,需要进行的一系列操作所带来的时间和空间的损耗,包括压栈、跳转、出栈等操作
- 在函数调用时,程序必须保存当前函数的现场信息,以便在返回时恢复现场。这些操作都需要消耗时间和空间资源,从而影响程序的执行效率和速度。
内联函数:
- 内联函数是一种特殊的函数,其函数体在调用处被嵌入,而不是通过函数调用进行执行
- 使用内联函数可以减少函数调用的开销,因为内联函数不需要在函数调用时进行栈帧的分配和销毁,也不需要进行函数跳转
- 可以提高程序的执行效率,特别是在需要频繁调用的场合
7.7 常量
常量
notes
简述
本节介绍常量,不过这一部分在介绍 C 语言时(2.4 常量与变量)就已经介绍过了。课件中描述的大部分知识点在文档 2.4 常量与变量 中都记录过。本节记录的一些点和之前记录的点有些许冲突。
常量
在 C++ 中,常量是一种不可改变的量,其值在程序运行时被确定并且不能再被修改。
C++ 中有两种类型的常量:字面常量和符号常量
- 字面常量(字面量 Literal)是程序中直接使用的值或者数据,包括整型常量、实型常量、字符常量、字符串常量等
- 符号常量指的是在程序中使用常量值时,将其定义为常量变量,通过变量名来代替常量值,可以使用 const 关键字来定义,也可以使用 #define 预处理指令来定义
int a = 10; // 整型常量
double b = 3.14; // 实型常量
char c = 'a'; // 字符常量
const char* str = "hello world"; // 字符串常量
10
、3.14
、'a'
、"hello world"
都是字面量
在程序中直接使用字面量的两个问题:
- 可读性差
- 可维护性差
const int MAX_NUM = 100; // 使用 const 关键字定义符号常量
#define PI 3.1415926 // 使用 #define 预处理指令定义符号常量
使用符号常量的好处是可以提高程序的可读性和可维护性,因为程序中使用的常量值都通过变量名来表示,方便修改和管理,而且语义化也更好一些。
100
、3.1415926
都是字面量,但是我们可以使用 MAX_NUM
、PI
符号来表示它们。
const int MAX_NUM = 100;
注意:在声明时一定要赋初值,而且在程序中间不能改变其值
const int MAX_NUM;
❌ 没有赋初值MAX_NUM = 101;
❌ 程序中对其进行了修改
#define PI 3.1415926
注意,使用宏定义来定义符号常量时:
- 不能使用赋值符
#define PI = 3.1415926
❌ - 不能以分号结尾
#define PI 3.1415926;
❌
用 #define
和用 const
定义符号常量的本质区别
- 用
#defined
定义的符号常量只在编译时完成宏替换(简单的字符串替换),在程序运行期间 不占内存空间 - 用
const
定义的符号常量在程序运行期间 占据内存空间,只是用 const 来指明该内存空间的只读约束
7.8 引用
引用
notes
简述
知道引用类型是什么
掌握引用类型的 3 种常见用法
认识引用类型和指针类型之间的差异
引用
- 同一个人,多个名字,C++ 叫做引用
- 引用就是给一个单元起一个别名
- 引用与它所引用的变量共享存储单元
- 引用是 C++ 中的一个重要概念,它可以看作是一个变量的别名,是一个在定义时被初始化的对象
- 引用变量被创建后,它与其所引用的变量始终保持同步,即它和被引用变量指向同一块内存地址,修改引用变量也会同时修改被引用变量
- 在 C++ 中,引用类型是一种特殊的数据类型,它是对已经存在的变量(实际上是内存地址)的另一个名字,即给已有变量起了一个别名,通过这个别名可以访问到已有变量的内容
- 使用引用类型能够简化一些程序的实现,避免复杂的指针操作,提高程序的可读性和可维护性
- 主要用法:
- 独立引用:减少数据复制所需的时间和内存空间
- 作为函数参数:可以避免传递大量的参数副本,提高程序的效率
- 作为函数返回类型:可以在函数中返回多个值
- 特点:
- 引用类型必须在定义时进行初始化,一旦初始化完成,就不能再绑定到其他变量
- 引用类型必须与其所绑定的变量的类型相同
- 引用类型不能有空值,即不能指向 NULL
- 引用的语法格式为:
type &ref_var = var;
- 引用与指针相比,具有更好的可读性和代码清晰度,并且可以避免指针的一些常见问题,例如空指针、野指针等。
定义引用类型的语法
引用的语法格式为:type &ref_var = var;
- type 表示引用的类型
- & 表示引用
- ref_var 表示引用变量名
- var 表示被引用的变量
int a = 10;
int& b = a; // 定义一个整型引用 b,它是 a 的别名
引用类型和指针类型之间的差异
引用和指针都是处理内存中的数据的重要工具,它们各有优点和不同的使用场景。在 C++ 中,建议优先使用引用来处理内存中的数据,因为引用更加直观、易于使用,且不会出现指针悬空等问题。但是,在某些情况下,指针仍然是必不可少的,比如需要动态分配内存时。
- 操作方式不同
- 指针变量存储的是内存地址,通过指针间接地访问内存中的数据,使用时需要使用取地址运算符 & 获取变量的地址,使用解引用运算符 * 访问指针指向的内存中的数据
- 引用变量和原变量使用同一块内存,通过引用直接访问内存中的数据,不需要取地址和解引用,使用引用就可以访问原变量(引用在使用时与被引用的变量没有区别,可以像变量一样进行操作)
- 存储方式不同
- 指针变量本身需要占用内存,存储的是被指向的变量的地址。指针变量可以指向空地址(NULL),表示不指向任何内存单元
- 引用变量不需要占用内存,本质上是目标变量的一个别名,它没有自己的地址,也不能指向空地址
- 初始化方式不同
- 指针变量可以在声明的同时进行初始化,也可以先声明后初始化,并且后续还可以改变指针变量的指向
- 引用变量必须在声明时初始化,且一旦初始化后不能改变
- 可以指向的对象不同
- 指针变量可以指向任意类型的变量,也可以指向堆、栈、静态数据区等
- 引用变量必须在定义时绑定到某个变量上,不能绑定到常量和表达式上,也不能绑定到临时变量上,且一旦绑定后续就无法再改变了
独立引用
#include <iostream>
using namespace std;
int main() {
int a = 10;
int& b = a;
cout << "a = " << a << endl; // 输出 a 的值
cout << "b = " << b << endl; // 输出 b 的值
cout << "修改 b 的值为 20" << endl;
b = 20; // 修改 b 的值
cout << "a = " << a << endl; // 输出 a 的值
cout << "b = " << b << endl; // 输出 b 的值
return 0;
}
/* 运行结果:
a = 10
b = 10
修改 b 的值为 20
a = 20
b = 20
*/
引用作为函数参数
#include <iostream>
using namespace std;
void func(int num) { num++; }
int main() {
int value = 5;
func(value);
cout << "value = " << value << endl; // => value = 5
return 0;
}
#include <iostream>
using namespace std;
void func(int* pnum) { (*pnum)++; }
int main() {
int value = 5;
func(&value);
cout << "value = " << value << endl; // => value = 6
return 0;
}
(*pnum)++;
注意:自增运算符 ++ 的优先级高于解引用运算符 ,因此这里不能写成 `pnum++`
#include <iostream>
using namespace std;
void swap(int &a, int &b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int x = 10, y = 20;
cout << "交换前:x = " << x << ", y = " << y << endl;
swap(x, y);
cout << "交换后:x = " << x << ", y = " << y << endl;
return 0;
}
/* 运行结果:
交换前:x = 10, y = 20
交换后:x = 20, y = 10
*/
swap 函数的参数使用了引用类型,这样在函数内部直接修改参数的值就能够实现值的交换。通过引用类型作为参数,可以避免拷贝参数带来的开销,提高代码的效率和性能。
引用作为函数的返回
#include <iostream>
using namespace std;
int& f(int* p) { return *p; }
int main() {
int a = 10, b;
b = f(&a) * 5;
f(&a) = 88;
cout << "b = " << b << endl;
cout << "a = " << a << endl;
return 0;
}
/* 运行结果:
b = 50
a = 88
*/
#include <iostream>
using namespace std;
int& getLargest(int arr[], int n) {
int maxIdx = 0;
for (int i = 1; i < n; i++) {
if (arr[i] > arr[maxIdx]) {
maxIdx = i;
}
}
return arr[maxIdx];
}
void printArr(int arr[], int n) {
cout << "打印 arr 中的成员:";
for (int i = 0; i < n; i++) {
cout << arr[i] << ' ';
}
cout << endl;
}
int main() {
int arr[] = {4, 7, 1, 8, 5};
const int arrLen = sizeof(arr) / sizeof(*arr);
int& maxVal = getLargest(arr, arrLen);
cout << "array 中的最大值是:" << maxVal << endl;
printArr(arr, arrLen);
maxVal = 100;
cout << "将 maxVal 设置为 100" << endl;
printArr(arr, arrLen);
int& newMaxVal = getLargest(arr, 5);
cout << "array 中的最大值是:" << newMaxVal << endl;
return 0;
}
/* 运行结果:
array 中的最大值是:8
打印 arr 中的成员:4 7 1 8 5
将 maxVal 设置为 100
打印 arr 中的成员:4 7 1 100 5
array 中的最大值是:100
*/
7.9 编程实战
打印菱形
#include <iostream>
using namespace std;
void printStar(int n);
int main() {
int n;
cout << "请输入菱形的长度(介于 1 ~ 5 之间的整数):";
cin >> n;
cout << endl;
if (cin.fail() || cin.get() != '\n') {
cout << "输入的数并非一个整数" << endl;
return 1;
}
if (n > 5 || n < 1) {
cout << "输入的数不在 1 ~ 5 之间" << endl;
return 1;
}
printStar(n);
}
void printStar(int n) {
int i, j;
for (i = 1; i <= n; ++i) {
for (j = 0; j < n - i; ++j) {
cout << " ";
}
for (j = 0; j < i; ++j) {
cout << "* ";
}
cout << endl;
}
for (i = 1; i <= n; ++i) {
for (j = 0; j < i; ++j) {
cout << " ";
}
for (j = 0; j < n - i; ++j) {
cout << "* ";
}
cout << endl;
}
}
请输入菱形的长度(介于 1 ~ 5 之间的整数):a
输入的数并非一个整数
请输入菱形的长度(介于 1 ~ 5 之间的整数):abc
输入的数并非一个整数
请输入菱形的长度(介于 1 ~ 5 之间的整数):0
输入的数不在 1 ~ 5 之间
请输入菱形的长度(介于 1 ~ 5 之间的整数):-1
输入的数不在 1 ~ 5 之间
请输入菱形的长度(介于 1 ~ 5 之间的整数):1
*
请输入菱形的长度(介于 1 ~ 5 之间的整数):2
*
* *
*
请输入菱形的长度(介于 1 ~ 5 之间的整数):3
*
* *
* * *
* *
*
请输入菱形的长度(介于 1 ~ 5 之间的整数):4
*
* *
* * *
* * * *
* * *
* *
*
请输入菱形的长度(介于 1 ~ 5 之间的整数):5
*
* *
* * *
* * * *
* * * * *
* * * *
* * *
* *
*
请输入菱形的长度(介于 1 ~ 5 之间的整数):100
输入的数不在 1 ~ 5 之间
请输入菱形的长度(介于 1 ~ 5 之间的整数):1.4
输入的数并非一个整数
请输入菱形的长度(介于 1 ~ 5 之间的整数):5.0
输入的数并非一个整数
int n;
cout << "请输入菱形的长度(介于 1 ~ 5 之间的整数):";
cin >> n;
cout << endl;
if (cin.fail() || cin.get() != '\n') {
cout << "输入的数并非一个整数" << endl;
return 1;
}
if (n > 5 || n < 1) {
cout << "输入的数不在 1 ~ 5 之间" << endl;
return 1;
}
校验逻辑分析:
- 通过 cin 读取用户输入的整数,并检查是否读取成功。如果读取失败,则说明输入的值并非整数,此时输出一条提示信息并返回错误代码 1。
- 使用 cin.get() 读取缓冲区中的下一个字符。如果下一个字符不是换行符(即用户输入的数字后面还有其他非空字符),则说明输入的值并非一个整数。此时同样输出一条提示信息并返回错误代码 1。
- 检查用户输入的数字是否介于 1 到 5 之间。如果不是,则同样输出一条提示信息并返回错误代码 1。
认识 cin.fail()
- cin.fail() 是 C++ 中 iostream 库的函数之一,用于检查前一个 cin 操作是否成功。
- 当 cin 操作遇到无法解析的输入时,会导致 cin 对象的状态位(state flag)被设置为 failbit,此时 cin.fail() 函数会返回 true。以上述程序为例,如果用户输入的是一个字符串而不是一个整数,cin.fail() 函数将返回 true。
- 一般情况下,我们在读取用户输入时使用
cin >> var
,并通过检查cin.fail()
来判断用户输入是否有效,从而在必要时提示用户并要求重新输入。这是一种比较常见的读取用户输入的方式。
评委打分
#include <iostream>
using namespace std;
int main() {
float x[8] = {0};
float aver(0), max(0), min(0);
for (int i = 0; i < 8; ++i) {
cin >> x[i];
if (x[i] > max) max = x[i];
if (x[i] < min || min <= 0) min = x[i];
aver += x[i];
// cout << x[i] << endl;
}
aver = (aver - max - min) / 6;
cout << aver << endl;
return 0;
}
expand函数
#include <iostream>
#include <string.h>
using namespace std;
void expand(const char* s, char* t) {
int i, j;
for (i = 0, j = 0; s[i] != '\0'; i++) {
switch (s[i]) {
case '\n':
t[j++] = '\\';
t[j++] = 'n';
break;
case '\t':
t[j++] = '\\';
t[j++] = 't';
default:
t[j++] = s[i];
break;
}
}
t[j] = '\0';
}
int main() {
const char* s1 = "hello\tworld\n23.04.14 09:46 am";
char s2[strlen(s1) + 1];
expand(s1, s2);
cout << s1 << endl;
cout << s2 << endl;
return 0;
}
/* 运行结果:
hello world
23.04.14 09:46 am
hello\t world\n23.04.14 09:46 am */
好记星
备注: 题目没太理解,主要就是借助这一题,了解 string、ifstream 的使用。
题目描述:
quite 相当 quiet 安静地
affect v 影响,假装 effect n 结果,影响
adapt 适应 adopt 采用 adept 内行
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <stdlib.h> // 包含 system 函数的声明
using namespace std;
int main() {
const int nrol = 3;
char str[80], strBook[100][80], c = 'y';
ifstream book;
char name[] = "myBook.txt";
book.open(name);
int i = 0, j, k;
while (book.getline(str, 80)) strcpy(strBook[i++], str);
book.close();
k = 0;
while(c != 'q') {
system("clear");
for(j = 0; j < nrol && k < i; ++j, ++k) cout << strBook[k] << endl;
if (k >= i) break;
cin >> c;
}
return 0;
}