一、地址和指针
1.1内存
内存由一系列连续的存储单元组成,每个存储单元都有计算机分配的 "编号" ——地址
定义一个变量,即是给变量分配从某一地址开始的若干存储单元,且通常以起始地址作为该变量的地址
注:内存空间的访问方式:通过变量名访问,通过地址访问
1.2针和指针变量
指针:一个变量的地址称为该变量的指针
指针变量:专门存放变量地址的变量(即指针)称为指针变量
二、指针变量
2.1始化
存储类型??数据类型? *指针名 = 初始地址
如int* a_ptr = &a;
注:
- 用变量地址作为初值时,该变量必须在指针初始化之前声明过,且变量类型应与指针类型一致
- 可以用一个已有合法值的指针去初始化另一个指针变量
- 不要用一个内部非静态变量去初始化?static?指针
2.2赋值
1、语法形式
指针名 = 地址
注:"地址"中存放的数据类型与指针类型必须相等
2、赋值为地址常量或变量,不能是普通整数
通过地址运算“&”求得已定义的变量和对象的起始地址
动态内存分配成功时返回的地址
3、指针空值nullptr
C++11 使用 nullptr 关键字,是表达更准确,类型安全的空指针
4、允许定义或声明指向?void?类型的指针
该指针可以被赋予任何类型对象的地址,如:void* general ;
#include <iostream> using namespace std; int main() { // void voidObject; 错,不能声明void类型的变量 void *pv; //对,可以声明void类型的指针 int i = 5; pv = &i; //void类型指针指向整型变量 int *pint = (int*)(pv); //void指针转换为int指针 cout << "*pint = " << *pint << endl; return 0; } 结果: *pint = 5
2.3指针类型算术运算
1、指针 p 加上或减去 n
指针当前指向位置的前方或后方第 n 个数据的起始位置
2、指针的 ++、-- 运算
指向前一个或后一个完整数据的起始位置
注:
运算的结果值取决于指针指向的数据类型,总是指向一个完整数据的起始位置
当指针指向连续存储的同类型数据时,指针与整数的加减运算和自增自减算才有意义
2.4指针类型关系运算
指向相同类型数据的指针之间可以进行各种关系运算
指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的
指针可以和 0 之间进行等于或不等于的关系运算,即?p == 0 或 p != 0
2.5指向指针的指针
语法形式
数据类型 **变量名
如char **p;
- p 是指针变量的定义形式
- p 前面的 * ,表示指针变量 p 是指向一个字符指针 变量 ?(即指向字符型数据的指针变量),* p 就是 p 所指向的另一个指针变量
三、指针与数组
指针运算比数组下标运算的速度快
数组名代表数组的首地址,也即第?0 号元素的地址p=a;?<=> p=&a[0];
数组元素可以看作是相应基本类型的普通变量
3.1指针运算
1、指针增减
当一个指针指向数组时,指针变量增加或减少某一数字,可以使指针向前或向后移动相应数字个元素
2、指针相减
当两个指针同时指向同一数组时,求它们的差所得到的结果是这两个指针之间的元素个数(一个带符号的整数)
3、指针比较大小
当两个指针同时指向同一数组时,比较它们的大小有意义。指向前面元素的指针变量 "小于" 指向后面元素的指针变量
4、指针相等
当两个指针指向同一数据元素,或者同时具有空指针值时,它们被认为是相等,否则被认为不相等
注:
任何两个同类型的指针可以比较相等和不相等?
任何指针都可以与空指针(NULL,0)比较相等或不相等?
3.2通过指针访问数组元素
1、p 指向数组 a 的首地址
-
- (p++) 与 * (++p) 作用不同
- (p++) 先取 * p 的值,后使 p 加1,得到 a[0] 的值
(++p) 先使 p 加1,再取 * p 的值,得到 a[1] 的值
(* p)++ 表示 p 所指向的元素值加1,即 (a[0]++)
2、p 指向数组 a 中的第 i 个元素
? ? * (p--) 相当于 a[i--],先对 p 进行 " * " 运算,再使 p 自减
? ? * (--p) 相当于 a[--i],先对 p 自减,再对 p 进行 " * " 运算
#include<iostream> using namespace std; int main() { int * p, a[10] = {1,2,3,4,5,6,7,8,9,0}; p = a; cout << *(p++) << ends; cout << *(++p) << ends; cout << (*p)++ << ends; cout << *(p--) << ends; cout << *(--p) << endl; return 0; } 结果: 1 3 3 4 1 //上述代码为未定义(UB)行为,标准中没有这种用法,属于错误代码,则结果由编译器决定
3.3指针数组
数组元素全为指针的数组称为指针数组
一维指针数组语法形式:数据类型? * 数组标识符[数组长度]? ? 如int? *ptr_arrey[10];
1、含义
指针数组中的元素亦可以表示为 * (* (ptr_array+i)),因为 "()" 的优先级较 " * " 高,且 " * " 右结合,因此可以写作 * * ( ptr_array + i )
2、运用
可以作为函数的参数使用,使用方式与普通数组类似
常适用于指向若干字符串,使字符串处理更加灵活方便
3、对比
1)与数组指针关系
数组指针是指向数组首元素的地址的指针,其本质为指针(该指针存放的是数组首地址,相当于2级指针,不可移动)
指针数组是数组元素为指针的数组,其本质为数组
注: * p[2] 是指针数组,实质是一个数组,里面的两个元素都是指针,[] 的优先级比 * 的优先级高,p 先与 [] 结合,形成数组 p[2],有两个元素的数组,再与 * 结合,表示此数组是指针类型的,每个数组元素相当于一个指针变量
2)?与二维数组对比
二维数组:如char string_1[10][10]
,只要定义了一个二维数组,无论赋不赋值,系统都会分配相应空间,而且该空间一定是连续的。其中每个元素表示一个字符,可以通过指定下标对其元素进行修改
指针数组:如 char? * str_B[5],系统至少会分配5个连续的空间用来存储5个元素,表示 str_B 是一个5个元素的数组,每个元素是一个指向字符型数据的一个指针
如:char a[3][8] = { "gain", "much", "strong" };
,char *n[3] = { "gain", "much", "strong" };
系统给数组?a 分配3×8的空间,而给 n 分配的空间取决于具体字符串的长度
相比于比二维字符数组,指针数组有明显的优点
- 指针数组中每个元素所指的字符串不必限制在相同的字符长度
- 访问指针数组中的一个元素是用指针间接进行的,效率比下标方式要高
但是二维字符数组可以通过下标很方便的修改某一元素的值,而指针数组无法这么做举例
四、指针与字符串
4.1字符串的表示形式
字符数组存放,如char string[] = "I love China!";
? ?注:在所有字符的最后加一个表示结束的空字符
字符指针指向,如char* string?= "I love China!";
?? 注:把字符串的首地址赋给 string
4.2字符指针作函数参数
用字符数组名或指向字符串的指针变量作参数
主调函数中的字符串会随着被调函数中字符串的改变而改变(因为传递的是地址)
注:用字符数组做形参,实参为字符指针;用字符指针做形参,实参为字符数组举例
4.3字符指针变量与字符数组
1、字符数组和指针变量的类型和大小不同
//字符数组由若干个元素组成,每个元素中放一个字符 char a[] = "I love China!"; //数组 a 占据 14 个字符的空间,存放字符序列“I love China!”的各个字符以及空字符 //字符指针变量中存放的是地址,只占据一个指针所需要的存储空间,而不是将字符串放到字符指针中 char *string = "I love China!"; //指针 string 只占据一个指针所需要的存储空间
2、赋值方式不同
//字符数组只能对各个元素分别赋值 char str[14]; str = "I love China!"; //非法 //字符指针可以整体赋值 char *string; string = "I love China!"; //赋值的是字符串的首地址
3、赋初值的含义不同
//字符数组 char str[14] = "I love China!"; //不等价于 char str[14]; str = "I love China!"; //数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值 //字符指针 char *string = "I love China!"; //等价于 char *string; string = "I love China!";
4、字符数组在编译时分配内存单元,有确定的地址;字符指针在编译时分配内存单元,放一个地址值而不是一个字符数据
char str[10]; scanf ("%s",str); //正确 char * a; scanf ("%s",a); //不正确,指针 a 的值不确定 //把指针的指向确定 char * str[10], * a; a = str; scanf ("%s",a); //正确
5、指针变量的值可以改变
指针变量是一个变量,可以在定义时确定指向字符串,也可以在后面的程序重新赋值,指向其它位置,或者被赋值为空指针
定义的数组名总表示它被分配的这块存储区域,不会表示别的地方,它本身不能被赋值
6、用指针指向一个格式字符串,可以替代 printf 函数中的格式字符串
char *format; format = "a = %d, b = %f\n"; pritnf(format, a, b); //等价于 pritnf("a = %d, b = %f\n", a, b);
五、指针与函数
函数本身不是变量,但可以定义指向函数的指针,这种指针可以被赋值、存放于数组之中、传递给函数以及作为函数返回值等
一个函数在编译时被分配给一个入口地址,这个入口地址称为函数的指针
5.1语法形式
数据类型 (* 函数指针变量 ) ( 形参列表?)
如double (* p1 ) ( int,?int );
5.2使用
1、赋值
在给函数指针变量赋值时,只需给出函数名,不必给出参数
如p1 = sin ;
?//使函数指针 p1 指向标准函数 sin,即将函数的入口地址赋给 p1
2、调用
用函数指针变量调用函数时,只需将 (* p ) 代替函数名即可,在后面的括弧中根据需要写上实参
如x = ?(* p1 )(3.14);
等价于x = sin(3.14);
//写在调用表达式中函数的位置,其效果就是调用被指针所指的函数
注:函数指针访问函数与通过函数名访问函数区别:
- 通过函数名使用函数,语句每次执行时,调用的总是同一个函数
- 通过函数指针使用函数,语句某次执行中使用的到底是哪个函数要看函数指针当时的值
5.3指针做函数参数
- 需要数据双向传递时(引用也可以达到此效果)
用指针作为函数的参数,可以使被调函数通过形参指针存取主调函数中实参指针指向的数据,实现数据的双向传递
- 需要传递一组数据,只传首地址运行效率比较高
实参是数组名时形参可以是指针
//读入三个浮点数,将整数部分和小数部分分别输出 #include<iostream> using namespace std; void splitFloat(float x, int *intPart, float *fracPart) { *intPart = static_cast<int>(x); //取x的整数部分 *fracPart = x - *intPart; //取x的小数部分 } int main() { for(int i = 0; i < 4; i++) { float x, f; int n; cin >> x; splitFloat(x, &n, &f); //变量地址作为实参 cout << "Integer Part = " << n << " Fraction Part = " << f << endl; } return 0; } 结果: 0.0 Integer Part = 0 Fraction Part = 0 1.0 Integer Part = 1 Fraction Part = 0 0.1 Integer Part = 0 Fraction Part = 0.1 1.1 Integer Part = 1 Fraction Part = 0.1
5.4返回指针的函数
语法形式存储类型?数据类型 * 函数名 ( 形参列表 )
- 不要将非静态局部地址用作函数的返回值??注:在子函数中定义局部变量后将其地址返回给主函数,就是非法地址
- 返回的指针要确保在主调函数中是有效、合法的地址
- 主函数中定义的数组,在子函数中对该数组元素进行某种操作后,返回其中一个元素的地址,这就是合法有效的地址
#include<iostream> using namespace std; int* search(int* a, int num); int main() { int array[5]; for(int i = 0; i < 5; i++) cin >> array[i]; int* zeroptr = search(array, 5); //将主函数中数组的首地址传给子函数 cout << zeroptr; return 0; } int* search(int* a, int num) { //指针a指向主函数中定义的数组 for(int i = 0; i < num; i++) if(a[i] == 0) return &a[i]; //返回的地址指向的元素是在主函数中定义的 } //函数运行结束时,a[i]的地址仍有 结果: 1 2 3 0 6 0x6ffe2c
- 在子函数中通过动态内存分配 new 操作取得的内存地址返回给主函数是合法有效的,但是内存分配和释放不在同一级别,注意不要忘记释放,避免内存泄漏
#include<iostream> using namespace std; int* newintvar(); int main() { int* intptr= newintvar(); *intptr = 5; //访问的是合法有效的地址 cout << *intptr << endl; delete intptr; //如果忘记释放,会造成内存泄漏 cout << *intptr << endl; cout << intptr; return 0; } int* newintvar () { int* p = new int(); return p; //返回的地址指向的是动态分配的空间 } //函数运行结束时,p中的地址仍有效 结果: 5 1727504 0x1a17d0 //二三行结果每次都不一样
5.5指向函数的指针
1、语法形式
存储类型? 数据类型? ( * 函数指针名 ) ( );
2、含义
函数指针指向的是程序代码存储区
3、典型用途——实现函数回调
- 通过函数指针调用函数
将函数的指针作为参数传递给另一个函数,使得在处理相似事件时可以灵活使用不同方法
- 调用者不必关心谁是被调用者,只需知道存在一个具有特定原型和限制条件的被调用函数
#include<iostream> using namespace std; int computer(int a, int b, int (*func)(int, int)) { return func(a, b); } int max(int a, int b) { return a > b ? a : b; } int min(int a, int b) { return a < b ? a : b; } int sum(int a, int b) { return a + b; } int main() { int x, y, res; cin >> x >> y; res = computer(x, y, &max); cout << "Max of " << x << " and " << y << " is " << res << endl; res = computer(x, y, &min); cout << "Min of " << x << " and " << y << " is " << res << endl; res = computer(x, y, &sum); cout << "Sum of " << x << " and " << y << " is " << res << endl; return 0; } 结果: 413 509 Max of 413 and 509 is 509 Min of 413 and 509 is 413 Sum of 413 and 509 is 922