目录
一、数组的定义与使用
1、数组的概念
数组是具有一定顺序关系的若干相同类型变量的集合体,组成数组的变量称为该数组的元素。
2、数组的定义
定义方式: 类型说明符 数组名[常量表达式]...;
数组名的构成方法与一般变量名相同
例如:
int a[10];
表示a为整型数组,有10个元素:a[0]…a[9]例如:
int a[5][3];
表示a为整型二维数组,其中第一维有5个下标(0~4),第二维有3个下标(0~2),数组的元素个数为15,可以用于存放5行3列的整型数据表格。注意事项:
①数组定义的基本语法
在大多数编程语言中,数组的定义需要明确数据类型和初始大小。例如在C语言中,定义数组时需要指定元素类型和数组长度:
int numbers[5]; // 定义一个包含5个整数的数组
②数组长度的确定
数组长度可以是固定值或变量,但某些语言如C99之前的标准要求长度必须是常量表达式。现代语言如Python的列表或JavaScript的数组则更灵活:
my_list = [1, 2, 3] # Python列表长度动态可变
③内存分配与初始化
静态数组在编译时分配内存,动态数组在运行时分配。未初始化的数组可能包含垃圾值,建议显式初始化:
int[] arr = new int[3]{1, 2, 3}; // Java初始化
④多维数组的处理
多维数组实际上是数组的数组。定义时需注意各维度的大小:
int[,] matrix = new int[2,3]; // C#二维数组
⑥边界检查
数组访问越界是常见错误。某些语言如Java会自动检查,而C/C++不会:
int arr[3] = {0}; arr[3] = 1; // 未定义行为
⑦数据类型一致性
数组元素通常要求同类型。若需混合类型,可使用结构体或对象数组:
let mixedArray = [1, "text", true]; // JS允许异质数组
⑧动态扩容机制
了解语言的数组扩容策略很重要。例如C++的vector会预留额外容量,Python列表会动态调整大小。
3、数组的使用
使用数组元素必须先声明,后使用。只能逐个引用数组元素,而不能一次引用整个数组
例如:a[0]=a[5]+a[7]-a[2*3]
例如:b[1][2]=a[2][3]/2
数组的输出方式:
例:将数组 int a[4]={1,2,3,4}全部输出for(int i=0;i<4;i++) cout << a[i];
练习题
定义int
类型的两个数组a
和b
,通过循环如示例输出一样打印反序的两个数组元素
示例输出:
a[0]=-1 b[0]=17a[1]=1 b[1]=15
a[2]=3 b[2]=13
a[3]=5 b[3]=11
a[4]=7 b[4]=9
a[5]=9 b[5]=7
a[6]=11 b[6]=5
a[7]=13 b[7]=3
a[8]=15 b[8]=1
a[9]=17 b[9]=-1
示例代码:#include <iostream> using namespace std; int main() { int a[10] = {-1, 1, 3, 5, 7, 9, 11, 13, 15, 17}; int b[10]; // 逆序赋值:a[0]->b[9], a[1]->b[8], ..., a[9]->b[0] for(int i = 0; i < 10; i++) { b[9 - i] = a[i]; // 用 9-i 作为 b 的索引 } // 输出结果(注意索引改为 i) for(int i = 0; i < 10; i++) { cout << "a[" << i << "]=" << a[i] << " "; cout << "b[" << i << "]=" << b[i] << endl; } return 0; }
★重要知识点:在 C++ 中,数组索引从 0 开始,有效范围是 0 到 数组长度 - 1。访问超出此范围的索引(如
a[10]
对于长度为 10 的数组)属于越界操作,会导致未定义行为(如程序崩溃、数据错误或安全漏洞)。关键规则总结:
- 数组长度为
n
时,索引范围是0
到n-1
。- 避免硬编码索引:使用循环变量(如
i < n
)而非固定值(如i <= 9
)。- 逆序赋值技巧:若要将
a
逆序存入b
,可使用b[n-1-i] = a[i]
。常见错误示例:
int a[10]; a[10] = 0; // 错误:越界(有效索引为0~9) for(int i=0; i<=10; i++) { ... } // 错误:循环会执行到i=10
正确写法:
…………………………牢记这一点可以避免 90% 的数组相关 Bug!…………………………int a[10]; for(int i=0; i<10; i++) { // 循环条件为 i < 10(即i最大为9) a[i] = ...; // 安全访问 }
二、数组的存储与初始化
1、一维数组的存储
数组元素在内存中顺次存放,它们的地址是连续的。元素间物理地址上的相邻,对应着逻辑次序上的相邻。
例如:
2、一维数组的初始化
定义:在定义数组时给出数组元素的初始值。
列出全部元素的初始值,例如:static int a[10]={0,1,2,3,4,5,6,7,8,9};
可以只给一部分元素赋初值,例如:static int a[10]={0,1,2,3,4};
在对全部数组元素赋初值时,可以不指定数组长度,例如:static int a[]={0,1,2,3,4,5,6,7,8,9}
一维数组的应用举例:
练习题1:
循环从键盘读取若干组选择题答案,计算并输出每组答案的正确率,直到输入ctrl+z为止;每组连续输入五个答案,每个答案可以是'a'、'd'.
#include <iostream> using namespace std; int main() { // 定义正确答案数组(使用const确保不可修改) const char key[] = {'a', 'c', 'b', 'a', 'd'}; // 存储5个问题的正确答案 const int NUM_QUES = 5; // 定义问题数量(使用const避免硬编码) char c; // 存储用户输入的当前字符 int ques = 0; // 当前处理的问题编号(索引) int numCorrect = 0; // 记录答对的题目数量 cout << "Enter the " << NUM_QUES << " question tests:" << endl; // 主循环:逐字符读取用户输入(包括换行符) while(cin.get(c)) { // cin.get(c)逐字符读取,包括空格和换行 if(c != '\n') { // 处理非换行字符(即用户输入的答案) if(c == key[ques]) { // 答案正确 numCorrect++; // 正确计数器+1 cout << " "; // 输出空格表示正确 } else { cout << "*"; // 输出星号表示错误 } ques++; // 移动到下一个问题索引 // 知识点: // 1. 数组索引从0开始,因此key[ques]对应第ques+1个问题的答案 // 2. const数组确保答案不会被意外修改 // 3. cin.get(c)与cin>>c的区别:前者读取所有字符(包括空格/换行),后者跳过空白字符 } else { // 处理换行符(即用户完成一组输入) // 输出得分(使用类型转换确保浮点数除法) cout << " Score:" << static_cast<float>(numCorrect)/NUM_QUES*100 << "%"; // 重置计数器,准备下一组输入 ques = 0; // 重置问题索引 numCorrect = 0; // 重置正确数量 cout << endl; // 换行 } // 知识点: // 1. static_cast<float>(numCorrect):将整数转换为浮点数,避免整数除法截断 // 2. 模块化思维:将处理逻辑分为"字符处理"和"换行处理"两个分支 // 3. 状态重置:每次换行后需要重置ques和numCorrect } return 0; }
核心代码思维解析:
输入处理:
- 使用
cin.get(c)
逐字符读取输入,包括换行符- 通过
c != '\n'
判断当前字符是答案还是换行符状态管理:
ques
作为数组索引跟踪当前问题编号(范围 0~4)numCorrect
累计正确答案数量- 每次换行后重置状态变量
输出逻辑:
- 实时反馈:正确输出空格,错误输出星号
- 换行时计算得分:使用
static_cast
确保浮点数除法涉及的关键 C++ 知识点:
数组与索引:
- 数组索引从 0 开始
- 使用 const 修饰常量数组
- 越界访问风险(本例中 ques 由 0 递增到 4,未超过 key 数组长度)
输入输出:
cin.get(c)
与cin >> c
的区别- 格式化输出:
cout << fixed << setprecision(2)
可控制小数位数类型转换:
static_cast<float>(int)
将整数转换为浮点数- 避免整数除法截断(如 5/5=1,而 5.0/5=1.0)
模块化设计:
- 使用 if-else 分支分离不同类型的输入处理
- 状态变量的初始化和重置
常量定义:
const int NUM_QUES
提高代码可维护性- 避免硬编码数字,便于后续修改问题数量
练习题2:
编程实现:编写函数
silly
,计算整数(不多于100
位)犯二的程度并返回。数字也可以“犯二”,一个整数“犯二的程度”定义为:
- 该数字中包含2的个数与其位数的比值;
- 如果这个整数是负数,则程度增加0.5倍;
- 如果还是个偶数,则再增加1倍。
代码:
#include <iostream> #include <cstring> using namespace std; double silly(char a[]); int main() { char s[102]; cin >> s; double rate = silly(s); // 取消注释以输出结果(题目中main函数未要求输出) // cout << "犯二率是" << rate << endl; return 0; } double silly(char a[]) { int count2 = 0; // 计数器:2的个数 int digits = 0; // 有效数字位数(不含符号) bool isNegative = (a[0] == '-'); // 是否为负数 int startIdx = isNegative ? 1 : 0; // 开始遍历的索引 // 统计2的个数和有效位数 for (int i = startIdx; a[i] != '\0'; i++) { digits++; if (a[i] == '2') count2++; } // 计算基础犯二率(避免整数除法) double rate = static_cast<double>(count2) / digits; // 应用修正系数 if (isNegative) rate *= 1.5; // 负数修正 // 末位是偶数则加倍(正确转换字符为数字) char lastChar = a[strlen(a) - 1]; if ((lastChar - '0') % 2 == 0) rate *= 2.0; // 调试输出 cout << "共有数字" << digits<<"个" << endl; cout << "其中共有数字“2”有" << count2<<"个" << endl; cout << "犯二率是" << rate << endl; return rate; }
练习题3:
编程实现:编写一个函数
king
,实现猴子选大王的功能。新猴王的选择方法是:让
N
只预定猴子围成一圈(最多100
只猴子),从某位置开始依次编号为1〜N
号。从第1
个号开始报数,每轮从1
个报到3
,凡报到3
的猴子即退出圈子,接着又从相邻的下一只猴子开始同样的报数。如此不断循环,最后剩下的一只猴子就选为猴王。代码:
#include <iostream> using namespace std; /** * 猴子选大王问题(约瑟夫环问题) * 参数: * a[] - 猴子数组,a[i] 非零表示第i只猴子在圈中,0表示已出圈 * n - 猴子总数(1-based索引) * 返回: * 最后剩下的猴子编号(1-based) */ int king(int a[], int n); int main() { int n; cout << "请输入猴子数量(n>0):"; cin >> n; // 初始化猴子数组:a[1]~a[n] 初始化为1~n,表示所有猴子在圈中 int a[1000] = {0}; for (int i = 1; i <= n; i++) { a[i] = i; } // 模拟出圈过程,返回最后剩下的猴子编号 int kingIndex = king(a, n); cout << kingIndex << "号猴子是大王" << endl; return 0; } int king(int a[], int n) { int out = 0; // 已出圈的猴子数量 int k = 0; // 当前报数计数(1-3循环) int next = 1; // 当前检查的猴子索引(1-based) // 循环直到只剩1只猴子(需出圈n-1次) while (out < n - 1) { // 跳过已出圈的猴子,寻找下一个在圈中的猴子 while (a[next] == 0) { if (++next > n) { next = 1; // 环形结构:超出尾部则回到头部 } } // 报数逻辑:每数到3时当前猴子出圈 if (++k == 3) { a[next] = 0; // 标记为出圈 out++; // 出圈数+1 k = 0; // 重置报数 } // 移动到下一只猴子(环形结构) if (++next > n) { next = 1; } } // 遍历数组,找到唯一未出圈的猴子 for (int i = 1; i <= n; i++) { if (a[i] != 0) { return i; // 返回猴子编号(1-based) } } return 0; // 理论上不会执行到这里 }
3、二维数组的存储
- 按行存放,例如:
float a[3][4];
可以理解为:
- 其中数组a的存储顺序为:a00 a01 a02 a03 a10 a11 a12 a13 a20 a21 a22 a23
4、二维数组的初始化
将所有初值写在一个{}内,按顺序初始化,例如:
static int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12
分行列出二维数组元素的初值,例如:
static int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
可以只对部分元素初始化,例如:
static int a[3][4]={{1},{0,6},{0,0,11}};
列出全部初始值时,第1维下标个数可以省略,例如:
static int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
或:static int a[][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
注意:
- 如果不作任何初始化,内部auto型数组中会存在垃圾数据,static数组中的数据默认初始化为0;
- 如果只对部分元素初始化,剩下的未显式初始化的元素,将自动被初始化为零;
三、数组作为函数参数
数组元素作实参,与单个变量一样。
数组名作参数,形、实参数都应是数组名(实质上是地址),类型要一样,传送的是数组首地址。对形参数组的改变会直接影响到实参数组。
示例:
编程实现:定义一个子函数
rowSum
,分别计算数组每一行的元素之和,然后在主函数中调用子函数rowSum
输出各行元素的和。#include <iostream> using namespace std; void rowSum(int a[][4], int b) { for(int i = 0; i < b; i++) { // 从第0行开始,到b-1行结束 for(int j = 1; j < 4; j++) // 遍历当前行的每一列 a[i][0] += a[i][j]; // 累加当前行的所有元素 } } int main(){ int table[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}}; rowSum(table, 3); //调用子函数,计算各行和 //输出计算结果 for (int i = 0; i < 3; i++) cout << "Sum of row " << i+1 << " is " << table[i][0] << endl; return 0; }
四、对象数组
1、对象数组的定义与访问
定义对象数组:
类名 数组名[元素个数]
;访问对象数组元素:
通过下标访问
和数组名[下标].成员名
2、对象数组初始化
数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。
通过初始化列表赋值。例:
Point a[2]={Point(1,2),Point(3,4)};
如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默认构造函数)。
3、数组元素所属类的构造函数
元素所属的类不声明构造函数,则采用默认构造函数。
各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。
各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。
当数组中每一个对象被删除时,系统都要调用一次析构函数。
五、 指针
1、内存空间的访问方式
通过变量名访问
通过地址访问
2、指针的概念
指针:内存地址,用于间接访问内存单元
指针变量:用于存放地址的变量
3、指针变量的定义
- 例:
static int i; static int* ptr = &i;
- 例:
*ptr = 3;
4、与地址相关的运算——“*”和“&”
指针运算符:*
地址运算符:&
六、指针变量的初始化和赋值
1、指针变量的初始化
语法形式:
存储类型 数据类型 *指针名=初始地址;
例:int *pa = &a;·
注意事项
- 用变量地址作为初值时,该变量必须在指针初始化之前已声明过,且变量类型应与指针类型一致。
- 可以用一个已有合法值的指针去初始化另一个指针变量。
- 不要用一个内部非静态变量去初始化 static 指针。
2、指针变量的赋值运算
语法形式:
指针名=地址
。注意:“地址”中存放的数据类型与指针类型必须相符向指针变量赋的值必须是地址常量或变量,不能是普通整数,例如:
- 通过地址运算“&”求得已定义的变量和对象的起始地址
- 动态内存分配成功时返回的地址
例外:整数0可以赋给指针,表示空指针。
允许定义或声明指向 void 类型的指针。该指针可以被赋予任何类型对象的地址。例:
void *general;
3、指针空值nullptr
以往用0或者NULL去表达空指针的问题:
- C/C++ 的NULL宏是个被有很多潜在BUG的宏。因为有的库把其定义成整数0,有的定义成 (void*)0。在C的时代还好。但是在C++的时代,这就会引发很多问题。
C++ 11使用nullptr关键字,是表达更准确,类型安全的空指针
4、指向常量的指针
不能通过指向常量的指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象。
例
int a; const int *p1 = &a; //p1是指向常量的指针 int b; p1 = &b; //正确,p1本身的值可以改变 *p1 = 1; //编译时出错,不能通过p1改变所指的对象
5、指针类型的常量
若声明指针常量,则指针本身的值不能被改变。
例
int a; int * const p2 = &a; p2 = &b; //错误,p2是指针常量,值不能改变
6、指针的算数运算、关系运算
①指针类型的算术运算
指针与整数的加减运算
指针++,-- 运算
指针p加上或减去n,其意义是指针当前指向位置的前方或后方第n个数据的起始位置。
指针的++、-- 运算,意义是指向下一个或前一个完整数据的起始。
运算的结果值取决于指针指向的数据类型,总是指向一个完整数据的起始位置。
当指针指向连续存储的同类型数据时,指针与整数的加减运和自增自减算才有意义。
例子:
#include <stdio.h> int main() { int arr[5] = {10, 20, 30, 40, 50}; int *ptr = arr; // 让ptr指向数组的起始位置 // 指针加上整数 printf("ptr + 2 指向的元素值为: %d\n", *(ptr + 2)); // 输出: 30 // 指针减去整数 ptr = &arr[4]; // 使ptr指向最后一个元素 printf("ptr - 3 指向的元素值为: %d\n", *(ptr - 3)); // 输出: 20 // 指针递增 ptr = arr; // 重新让ptr指向数组起始位置 ptr++; // 此时ptr指向arr[1] printf("ptr 递增后指向的元素值为: %d\n", *ptr); // 输出: 20 // 指针递减 ptr--; // 此时ptr指向arr[0] printf("ptr 递减后指向的元素值为: %d\n", *ptr); // 输出: 10 // 指针相减(计算两个指针之间的元素个数) int *ptr1 = &arr[0]; int *ptr2 = &arr[4]; printf("ptr2 和 ptr1 之间相差的元素个数为: %td\n", ptr2 - ptr1); // 输出: 4 return 0; }
②指针与整数相加的意义
③指针类型的关系运算
指向相同类型数据的指针之间可以进行各种关系运算。
指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的。
指针可以和零之间进行等于或不等于的关系运算。例如:
p==0或p!=0
示例:
#include <stdio.h> int main() { int arr[5] = {10, 20, 30, 40, 50}; int *ptr1 = &arr[0]; int *ptr2 = &arr[2]; int *ptr3 = &arr[4]; // 大于(>)运算 if (ptr3 > ptr1) { printf("ptr3 的地址比 ptr1 大\n"); // 会执行此语句 } // 小于(<)运算 if (ptr1 < ptr2) { printf("ptr1 的地址比 ptr2 小\n"); // 会执行此语句 } // 等于(==)运算 if (ptr1 == &arr[0]) { printf("ptr1 指向数组的起始位置\n"); // 会执行此语句 } // 大于等于(>=)运算 ptr1 = ptr3; // 让ptr1指向ptr3所指的位置 if (ptr1 >= ptr3) { printf("ptr1 的地址大于或等于 ptr3\n"); // 会执行此语句 } // 遍历数组(利用关系运算控制循环) printf("遍历数组的结果:"); for (ptr1 = arr; ptr1 < arr + 5; ptr1++) { printf("%d ", *ptr1); // 输出: 10 20 30 40 50 } printf("\n"); return 0; }
七、用指针访问数组元素
1、用指针访问数组元素
- 数组是一组连续存储的同类型数据,可以通过指针的算术运算,使指针依次指向数组的各个元素,进而可以遍历数组。
2、定义指向数组元素的指针
定义与赋值,例:
int a[10], *pa; pa=&a[0]; 或 pa=a;
等效的形式
- 经过上述定义及赋值后,
*pa
就是a[0]
,*(pa+1)
就是a[1]
,… ,*(pa+i)
就是a[i]
。a[i], *(pa+i), *(a+i), pa[i]
都是等效的。注意:不能写 a++,因为a是数组首地址、是常量。
例子
#include <stdio.h> int main() { int arr[5] = {10, 20, 30, 40, 50}; // 定义一个包含5个整数的数组 int *ptr; // 声明一个指向整数的指针 // 使指针指向数组的第一个元素(索引为0) ptr = &arr[0]; // 也可以直接用数组名赋值,因为数组名会隐式转换为指向首元素的指针 // ptr = arr; // 输出指针指向的元素值 printf("ptr 指向的元素值是: %d\n", *ptr); // 输出: 10 // 移动指针到数组的第三个元素(索引为2) ptr = &arr[2]; printf("移动后 ptr 指向的元素值是: %d\n", *ptr); // 输出: 30 // 通过指针修改数组元素的值 *ptr = 300; printf("修改后 arr[2] 的值是: %d\n", arr[2]); // 输出: 300 return 0; }
八、指针数组
1、指针数组
- 数组的元素是指针型
2、指针数组与二维数组对比
①指针数组与二维数组的定义
- 指针数组是一个数组,其元素均为指针。例如,
int *arr[3]
表示一个包含3个指针的数组,每个指针指向一个整数。- 二维数组是一个连续的内存块,按行和列组织数据。例如,
int arr[3][4]
表示3行4列的整数数组。②内存布局差异
- 指针数组中每个元素是一个独立的指针,可以指向不同长度的内存块。内存分配不连续,灵活性高。
- 二维数组在内存中是连续存储的,所有元素按行优先或列优先顺序排列。固定大小,内存连续。
③访问方式对比
- 指针数组通过双重解引用访问元素,例如
*(arr[i] + j)
或arr[i][j]
。- 二维数组直接通过下标访问,例如
arr[i][j]
,编译器自动计算偏移量。④灵活性比较
- 指针数组可以动态分配每一行的大小,适合不规则数据(如每行长度不同)。
- 二维数组大小固定,必须在编译时确定,适合规则数据。
⑤性能考量
- 二维数组由于内存连续,缓存命中率高,访问速度通常更快。
- 指针数组可能因内存分散导致缓存效率低,但动态分配更灵活。
⑥适用场景
- 指针数组适合需要动态调整行大小或处理不规则数据的场景。
- 二维数组适合固定大小、需要高效访问的矩阵或表格数据。
代码示例
指针数组示例:
int *ptrArr[3]; for (int i = 0; i < 3; i++) { ptrArr[i] = malloc(4 * sizeof(int)); } ptrArr[1][2] = 5; // 访问元素
二维数组示例:
int matrix[3][4]; matrix[1][2] = 5; // 访问元素
总结
指针数组提供动态灵活性,但牺牲部分性能;二维数组结构简单高效,但缺乏动态调整能力。选择取决于具体需求。
3、不同数据类型的指针数组案例。
① 整数指针数组
#include <stdio.h> int main() { int a = 10, b = 20, c = 30; // 定义一个整数指针数组 int *ptr_array[3]; // 将指针指向各个整数变量 ptr_array[0] = &a; ptr_array[1] = &b; ptr_array[2] = &c; // 通过指针数组访问值 for(int i = 0; i < 3; i++) { printf("ptr_array[%d] = %p, *ptr_array[%d] = %d\n", i, ptr_array[i], i, *ptr_array[i]); } return 0; }
②字符串指针数组(字符指针数组)
#include <stdio.h> int main() { // 定义一个字符指针数组,用于存储多个字符串 const char *names[] = { "Alice", "Bob", "Charlie", "David", NULL // 作为结束标记 }; // 遍历字符串数组 for(int i = 0; names[i] != NULL; i++) { printf("Name %d: %s\n", i+1, names[i]); } return 0; }
③函数指针数组
#include <stdio.h> // 几个简单的函数 void sayHello() { printf("Hello!\n"); } void sayGoodbye() { printf("Goodbye!\n"); } void sayName() { printf("My name is Function.\n"); } int main() { // 定义一个函数指针数组 void (*func_ptr_array[])() = {sayHello, sayGoodbye, sayName}; // 通过数组调用函数 for(int i = 0; i < 3; i++) { printf("Calling function %d: ", i+1); func_ptr_array[i](); } return 0; }
④动态分配的指针数组
#include <stdio.h> #include <stdlib.h> int main() { int size = 5; // 动态分配一个指针数组 int **ptr_array = (int **)malloc(size * sizeof(int *)); // 为每个指针分配内存并赋值 for(int i = 0; i < size; i++) { ptr_array[i] = (int *)malloc(sizeof(int)); *ptr_array[i] = i * 10; } // 打印值 for(int i = 0; i < size; i++) { printf("ptr_array[%d] = %p, *ptr_array[%d] = %d\n", i, ptr_array[i], i, *ptr_array[i]); } // 释放内存 for(int i = 0; i < size; i++) { free(ptr_array[i]); } free(ptr_array); return 0; }
九、以指针作为函数参数
1、为什么需要用指针做参数?
需要数据双向传递时(引用也可以达到此效果)
用指针作为函数的参数,可以使被调函数通过形参指针存取主调函数中实参指针指向的数据,实现数据的双向传递
需要传递一组数据,只传首地址运行效率比较高
实参是数组名时形参可以是指针
2、指针类型的函数
指针函数的定义形式
若函数的返回值是指针,该函数就是指针类型的函数。
定义形式
存储类型 数据类型 *函数名() { //函数体语句 }
注意
- 不要将非静态局部地址用作函数的返回值。错误的例子:在子函数中定义局部变量后将其地址返回给主函数,就是非法地址
- 返回的指针要确保在主调函数中是有效、合法的地址。正确的例子:主函数中定义的数组,在子函数中对该数组元素进行某种操作后,返回其中一个元素的地址,这就是合法有效的地址
- 返回的指针要确保在主调函数中是有效、合法的地址。正确的例子:在子函数中通过动态内存分配new操作取得的内存地址返回给主函数是合法有效的,但是内存分配和释放不在同一级别,要注意不能忘记释放,避免内存泄漏
3、指向函数的指针
函数指针的定义
定义形式:
存储类型 数据类型 (*函数指针名)();
含义:函数指针指向的是程序代码存储区。
函数指针的典型用途——实现函数回调
通过函数指针调用的函数。例如将函数的指针作为参数传递给一个函数,使得在处理相似事件的时候可以灵活的使用不同的方法。
调用者不关心谁是被调用者,需知道存在一个具有特定原型和限制条件的被调用函数。
十、动态分配与释放内存
1、动态申请内存操作符 new
new 类型名T(初始化参数列表)
功能:在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表赋以初值。
结果值:成功:T类型的指针,指向新分配的内存;失败:抛出异常。
2、释放内存操作符delete
delete 指针p
功能:释放指针p所指向的内存。p必须是new操作的返回值。
3、分配和释放动态数组
分配:new 类型名T [ 数组长度 ]
- 数组长度可以是任何表达式,在运行时计算
释放:delete[] 数组名p
- 释放指针p所指向的数组。
- p必须是用new分配得到的数组首地址。
例:为了动态声明一个内容为”string”的字符串
char *str=new char[10]; strcpy(str,"string");
十一、智能指针
1、智能指针
显式管理内存在是能上有优势,但容易出错。
C++11提供智能指针的数据类型,对垃圾回收技术提供了一些支持,实现一定程度的内存管理
2、C++11 的智能指针
unique_ptr :不允许多个指针共享资源,可以用标准库中的move函数转移指针
shared_ptr :多个指针共享资源
weak_ptr :可复制shared_ptr,但其构造或者释放对资源不产生影响
十二、vector对象
1、为什么需要vector?
封装任何类型的动态数组,自动创建和删除。
数组下标越界检查。
ArrayOfPoints也提供了类似功能,但只适用于一种类型的数组。
2、vector对象的定义
vector<元素类型> 数组对象名(数组长度);
例:
vector<int> arr(5)
,建立大小为5的int数组3、vector对象的使用
对数组元素的引用
- 与普通数组具有相同形式:vector对象名 [ 下标表达式 ]
vector数组对象名不表示数组首地址
- 获得数组长度
- 用size函数:数组对象名.size()
案例
使用
vector
存储和遍历学生成绩以下是一个使用
std::vector
存储学生成绩并计算平均分的案例:#include <iostream> #include <vector> int main() { // 创建 vector 存储成绩 std::vector<int> scores = {85, 90, 78, 92, 88}; // 计算总分 int sum = 0; for (int score : scores) { sum += score; } // 计算并输出平均分 double average = static_cast<double>(sum) / scores.size(); std::cout << "平均分: " << average << std::endl; return 0; }
使用
vector
动态添加数据
vector
可以动态调整大小,适用于运行时不确定数据量的场景:#include <iostream> #include <vector> int main() { std::vector<std::string> names; // 动态添加数据 names.push_back("Alice"); names.push_back("Bob"); names.push_back("Charlie"); // 遍历输出 for (const auto& name : names) { std::cout << name << std::endl; } return 0; }
使用
vector
进行排序可以利用
<algorithm>
库对vector
进行排序:#include <iostream> #include <vector> #include <algorithm> int main() { std::vector<int> numbers = {5, 2, 8, 1, 3}; // 升序排序 std::sort(numbers.begin(), numbers.end()); // 输出排序结果 for (int num : numbers) { std::cout << num << " "; } return 0; }
使用
vector
存储自定义对象
vector
可以存储自定义类或结构体:#include <iostream> #include <vector> struct Point { int x; int y; }; int main() { std::vector<Point> points = {{1, 2}, {3, 4}, {5, 6}}; // 遍历输出点坐标 for (const auto& p : points) { std::cout << "(" << p.x << ", " << p.y << ")" << std::endl; } return 0; }
这些案例展示了
vector
在存储、遍历、动态调整和排序方面的灵活性,适用于多种编程场景。
十三、string类
1、string类
使用字符串类string表示字符串
string实际上是对字符数组操作的封装
2、string类常用的构造函数
string();
//默认构造函数,建立一个长度为0的串。例:string s1;
string(const char *s);
//用指针s所指向的字符串常量初始化string对象。例:string s2 = “abc”;
string(const string& rhs);
//复制构造函数。例:string s3 = s2;
3、string类常用操作
s + t
将串s和t连接成一个新串
s = t
用t更新s
s == t
判断s与t是否相等
s != t
判断s与t是否不等
s < t
判断s是否小于t(按字典顺序比较)
s <= t
判断s是否小于或等于t (按字典顺序比较)
s > t
判断s是否大于t (按字典顺序比较)
s >= t
判断s是否大于或等于t (按字典顺序比较)
s[i]
访问串中下标为i的字符例:
string s1 = "abc", s2 = "def"; string s3 = s1 + s2; //结果是"abcdef" bool s4 = (s1 < s2); //结果是true char s5 = s2[1]; //结果是'e'
4、考虑:如何输入整行字符串?
* 用cin的>>操作符输入字符串,会以空格作为分隔符,空格后的内容会在下一回输入时被读取
- 输入整行字符串
- getline可以输入整行字符串(要包string头文件),例如:getline(cin, s2);
- 输入字符串时,可以使用其它分隔符作为字符串结束的标志(例如逗号、分号),将分隔符作为getline的第3个参数即可,例如:getline(cin, s2, ‘,’);