#4 数组和指针
程序员文章站
2024-01-03 10:20:28
...
目录
1. 数组和指针
//优先使用 容器 使用STL
1.1. 数组
1.1.1. 数组简介
- 简介
1. 数组下标从0开始; 2. 数组是可以在内存中连续存储多个元素的结构; 1. 数组中的所有元素必须属于相同的数据类型; 3. 数组必须先声明,然后才能使用; 1. 声明一个数组只是为该数组留出内存空间,都不会为其赋任何值; 4. 数组的元素通过数组下标访问; 5. 一维数组可用一个环动态初始化,而二维数组可用嵌套环动态初始化; 6. 二维数组可以看作是由一维数组的嵌套而构成的; 7. vector容器提供了比数组更好的安全性和灵活性;
1.1.2. 一维数组
- 初始化一维数组
- 后面的元素个数与声明的一致
int year[ 3 ] = { 1, 2, 3};
- 后面的元素个数与声明的一致
- 后几个元素可以不初始化,默认值为0
int months [ 12 ] = { 1, 3, 5, 7, 8, 9, 10, 12}
// 后面5个元素为0 - 元素的个数不指定
int days[ ] = { 1, 15 }; //后面元素的个数 与 前面中括号中的数字 至少有一个 int array [ ] = { }; // 错误,个数未指明
-
动态设置数组的大小
- Sizeof命令 用数组的总大小/数组中某个元素的大小
Sizeof(数组名num)/ sizeof(单个数组元素 num[0]) 注:只适合基本类型,不适合string类型,适合int、double等类
- Sizeof命令 用数组的总大小/数组中某个元素的大小
- 下标
index maxindex // max为数组中的最大值,计算得到
1.1.3. 二维数组
- 格式:
[ ][ ] 例:A[ ][ ]
1.1.4. 数组的排序算法
-
冒泡排序
1. 第一轮比较的次数:数组的总长度-1 2. 下一轮比上一轮比较的次数,少一次 3. 采样 嵌套 for循环 //外层控制循环的轮数,内层控制每轮的比较和交换;
-
选择排序 // 利用交换
1. 假设最小值为数组中的第一个数字 2. 比较,选择数组中的最小的数字 3. 交换下标 //保证比较中的最小值 下标最小
1.1.5. 数组的删除与插入
- 数组书写规则
- 数组的大小一旦确定,就不能更改 // double power[ 33 ]
- 并直接对数组进行初始化,不可写成两句
即 power[33] = {0, 1, …… }
1.1.6. 数组的替代品
//重要
1.1.6.1. 向量容器vector //vector
-
特性
1. 动态数组,可在运行阶段设置长度 2. 具有数组的快速索引方式 3. 可以插入和删除元素 4. 需要引用 #include < vector >
-
定义和初始化
1. Vector <double> vec1; // 只存放double类型 2. Vector < string > vec2 (5); // 指定五个数据 3. Vector < int > vec3 (20, 998); // 指定20个数据并初始化为998
-
常用操作
-
程序示例
Vecdouble.size() //容器的大小 // 集合的通用遍历方法:使用迭代器 iterator**; // 以下是迭代器的基本用法 vector<double>::iterator it ; // 得到迭代器对象---->实际上是一个**指针** :: //[域运算符](https://zhidao.baidu.com/question/226903131.html) // 从第一个元素开始迭代 for (it = vecdouble.begin(); it != vecdouble.end(); ++it) { cout << *it << endl; } //==排序== 使用#include <algorithm> // algorithm 算法 // 使用 sort 函数进行排序 sort(vecdouble.begin(); vecdouble.end()); //排序 rverse(vecdouble.begin(); vecdouble.end()); //对结果进行逆序排序 for (it = vecdouble.begin(); it != vecdouble.end(); **++it**) { cout << *it << endl; //输出 } //++it 可以减少缓存
-
实战
-
返回 Vector 下标
使用 两个迭代器进行运算,返回迭代器间隔
使用 *find()*函数,查找相应数值*代码* //printf 函数是输出,定义的一个cout函数 int main() { const int ARRAY_SIZE = 8; int IntArray[ARRAY_SIZE] = { 1,2,3,4,5,6,7 }; vector<int> myvt; vector<int>::iterator location_index; for (int i = 0;i < 8;++i) { myvt.push_back(IntArray[i]); } for_each(myvt.begin(), myvt.end(), print); location_index = find(myvt.begin(), myvt.end(), 2); //find函数 cout << "数字2的下标是:" << (location_index - myvt.begin()) << endl; location_index = find_if(myvt.begin(), myvt.end(), bind2nd(greater<int>(), 5)); cout << "第一个大于5的数字的下标是:" << (location_index - myvt.begin()) << endl; return 0; }
参考
-
-
排序后引用原来的下标
可以使用struct来绑定两个属性,即数值和下标
可以继续使用vector的sort()函数;参考
1.1.7. 数组的应用实例
- 求数组中的最值
- 定义一个整形数组,赋值后求其中奇数个数和偶数个数
1.2. 指针(Pointer)
1.2.1. 指针
- 简介
1. 指针是一个值为其他对象内存地址的**变量** 2. 指针变量可以赋值,指针的指向在程序执行中可以改变 3. 指针不能与现有变量重名 4. 若指针已声明指向某种数据类型的地址,则它不能存储其他数据类型的地址。 5. char 型指针在C++中默认为字符串了
- 基本用法
- 数据类型* 指针变量名 //注意书写位置 例:int* p 而非int *p
前者写法偏向于地址,即p是一个地址变量,表示一个十六进制的地址; 后者写法偏向于值,*p是一个整型变量,表示一个整数值。
- 声明中和语句中的意义不同
1. 声明中是定义指针变量 2. 语句中是引用变量
- 取变量地址 &
- 数据类型* 指针变量名 //注意书写位置 例:int* p 而非int *p
- 空指针
1. 空指针不指向任何对象,在试图使用一个指针之前可以首先检查是否为空 2. Int *ptr1 = nullptr或null;// 等同于int *ptr1 = 0 3. 指针的使用一定要初始化,并在可能的情况下, 尽量在定义了对象之后再定义指向它的指针。
- Void *指针
1. 一种特殊的指针类型,可以**存放任意对象**的指针。 2. 存放一个内存地址,地址指向的内容是什么类型不能确定 3. Void* 类型指针一般用来与 其他指针 比较,作为函数的输入和输出;赋值给另一个void*指针。
- 注意
1. 在使用指针时,数组名即为连续内存单元的首地址; 2. 不能直接将地址赋值给指针,只能用&取地址; 3. 指针的运算局限于++和--,其他运算无实际意义;
1.2.2. 指针的算术运算
- 数组名不能用于算术运算
即:int num[ 50 ]; *++num //错误 只能使用*++ptr_num 前面已经定义
1.3. 引用(Reference)
为对象起了另一个名字。 &
参考资料
1.3.1. 语句格式
- 定义
1. 引用变量是 *一个别名*,也就是说,它是某个 *已存在变量的另一个名字*。 一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。 2. 语句格式 数据类型 **&**引用名称 = 目标对象; int**&** r = i; double**&** s = d; //注意写法
- 注意事项
1. 引用并非对象,只是为一个已经存在的对象起的别名; 2. 引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起 例:int **&**ref_value = 10 //错误 指向常量的引用是非法的 指向常量时需加 const
- 引用必须初始化,所以使用引用之前不需要测试其有效性,
因此使用引用可能比使用指针效率更高。
1.3.2. 指针与引用的区别
-
指针和引用 区别
1. 引用对指针进行了简单封装,底层仍然是指针 2. 获取引用地址时,编译器会进行内部转换 3. 实际用起来,可以少写几个*
-
const string 与 const string& (C++中的引用) 区别
1. 不带&的是一个**常对象**,带&是一个**常引用**; 2. 前者是指针,后者是引用。**指针**指向一块内存, 它的内容是所指内存的地址;**引用**是某块内存的别名。
参考资料:
1.4. 数组与指针案例实战
-
注意
- 在数组中,利用指针进行遍历,若超过其定义范围,则会连接到随机数,给程序运行带来隐患。
- 可以利用数组个数i来限制。例:*(p_arrays+i)
-
实例
- 数组的逆序
//利用指针 - 使用指针创建二维数组
int *p = new int[10]; //使用指针创建二维数组 int(*p2)[3] = new int[5][3]; p2[3][2] = 998; for (int i = 0; i < 5;i++) { for (int j = 0; j < 3;j++) { cout << *(*(p2 + i) + j) << ','; } cout << endl;
- 数组的逆序
1.5. 动态分配内存
-
内存之堆内存与栈内存
-
堆内存是用来存放动态申请的内存都存放在堆内存
//由程序员掌控 new的东西都在堆区 - 栈内存是用来存放在函数中定义的一些基本类型的变量和对象的引用变量
//由系统控制
-
堆内存是用来存放动态申请的内存都存放在堆内存
1.5.1. 动态分配内存
- 简介堆内存
- 使用new分配内存
- 使用delete释放内存
- 说明
1.在运行阶段为一个int值分配未命名的内存 2.使用指针访问(指向)这个值(右左) int* ptr_int = new int; delete ptr_int; //释放new分配的内存
- 注意
-
不要创建两个指向同一块内存的指针,有可能误删两次
int *ptr = new int; int * ptr1 = ptr; delete ptr; delete ptr1;
-
不要创建两个指向同一块内存的指针,有可能误删两次
1.5.2. 动态分配的数组
- 语法简介
1. 使用new创建动态分配数组 int *intArray = new int[10]; //New运算符返回第一个元素的地址 2. delete[] 释放内存 delete []intArray; //释放整个数组
- New和delete的使用规则
1. 不要使用delete释放不是new分配的内存,成对使用 2. 不要使用delete释放同一内存两次 3. 如果使用new[ ]为数组分配内存,则对应delete[ ]释放内存 4. 对空指针使用delete是安全的
1.5.3. 程序的内存分配
-
堆和栈的区别 之 数据结构和内存
- 堆和栈的区别 之 数据结构和内存 //很重要 必看 by叫我大侠
-
类型介绍 //内存中的区别
1. 栈区 stack 1. 由编译器自动分配释放,一般存放函数的参数值、局部变量值等 2. 操作方式类似数据结构中的栈——先进后出 2. 堆区 heap 1. 一般由程序员分配释放,若程序不释放,程序结束时可能由系统回收 2. 注意:**与数据结构中堆是两回事**,分配方式类似链表 3. 全局区(静态区——static) 1. 全局变量和静态变量存储在一起 2. 程序结束由系统释放 4. 文字常量区 1. 常量字符串就放在这里,程序结束由系统释放
- 代码程序区
- 存放函数体的二进制代码
存储区介绍:
- 存放函数体的二进制代码
- 代码程序区
-
实例说明
栈区是说的;堆区干很多活的,自己分配内存的int num = 90; //栈区 int * p = new int; //堆区 自己动手 *p = 90;
int num1 = 0; //全局初始化区 int* ptr1; //全局未初始化区 int main() { //栈区 int num2; //栈区 char str[] = "老九君"; //栈区 char *ptr2; //老九君以及\0 在常量区,ptr3在栈区; char *ptr3 = "老九君"; //全局(静态)初始化区 static int num3 = 1024; //分配的内存在栈区; ptr1 = new int[10]; ptr2 = new char[20]; //注意:ptr1 和 ptr2 本身是在栈区中的; return 0; }