当前位置 : 主页 > 编程语言 > c++ >

C++——指针

来源:互联网 收集:自由互联 发布时间:2021-06-23
一、地址和指针 1.1内存 内存由一系列连续的存储单元组成,每个存储单元都有计算机分配的 "编号" ——地址 定义一个变量,即是给变量分配从某一地址开始的若干存储单元,且通常以

一、地址和指针

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返回指针的函数

语法形式存储类型?数据类型 * 函数名 ( 形参列表 )

  • 不要将非静态局部地址用作函数的返回值??注:在子函数中定义局部变量后将其地址返回给主函数,就是非法地址
  • 返回的指针要确保在主调函数中是有效、合法的地址
  1. 主函数中定义的数组,在子函数中对该数组元素进行某种操作后,返回其中一个元素的地址,这就是合法有效的地址
#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
  1. 在子函数中通过动态内存分配 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
网友评论