如何理解指针(如何理解指针的加法运算)

转载请注明,原文地址:

http://www.lgygg.wang/lgyblog/2019/10/22/%e6%8c%87%e9%92%88/

1.什么是指针

指针是一个变量,其值为另一个变量的地址,即该变量内存位置的直接地址。所有的变量其实有点类似键值对Key-Value,例如

int i = 1; //这里相当于i是Key,1是Value,类似于键值对,我们可以通过i拿到值“1”。
String str = “lgy”; //str相当于Key,”lgy”相当于Value,可以通过str拿到字符串“lgy”。
指针也是个变量,他和其他变量不同的是它存储的是另一个变量的内存地址。如下例子
int i = 1; //i是一个整型,它存储的是一个整型数字。 
String str = “lgy”; //str是一个String类型,存储的是一个字符串。

下面通过例子说明指针

int j = 2;
int *_data = j;

我们可以看到,指针变量除了存储的值和其他变量有所不同之外,它的声明也不一样,需要再变量名之前加个“”号,虽然指针是一种变量,但是它还是要定义数据类型的,而且它必须要和它存储的变量地址的数据类型是一致的,如上“j”是一个整型,所有这种_data也必须声明为int。这是为什么呢?具体原因我不清楚,但是从使用指针访问变量地址所存储的值的操作来看,是有必要将指针的数据类型定义成它指向的内存地址存储的数据类型一致。还是通过上面的例子来说明,我们如何通过指针*_data来获取j变量的值呢?

_data //通过_data可以拿到j的内存地址
*_data //通过*_data可以拿到j的值,即2

通过_data拿到j的值,如果不定义_data为int,那么*_data拿到的值编译器就无法分辨它是什么数据类型。这只是个人的理解。

2.如何使用指针

1)声明指针

指针变量声明的一般形式为:

type *var-name;

type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int *ip; /* 一个整型的指针 */ 
double *dp; /* 一个 double 型的指针 */ 
float *fp; /* 一个浮点型的指针 */ 
char *ch; /* 一个字符型的指针 */

2)使用指针

使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。

        #include 

        using namespace std;

        int main()
        {
            int  var = 20;   // 实际变量的声明
            int* ip;        // 1.指针变量的声明

            ip = &var;       // 2.在指针变量中存储 var 的地址

            cout << "Value of var variable: ";
            cout << var << endl;

            // 输出在指针变量中存储的地址
            cout << "Address stored in ip variable: ";
            cout << ip << endl;

            // 3.访问指针中地址的值
            cout << "Value of *ip variable: ";
            cout << *ip << endl;

            return 0;
        }

结果输出:

3.多级指针

指针的本质就是一个普通变量,它的值表示的是一个内存地址,这个内存地址中可能存放了其它变量。那么二级指针其实也是一个普通的变量,这个变量中同样也存放了一个内存地址,而这个内存地址是一个指针变量的地址。例如:

int a = 0;
int b = 1;
int *p = &a;
int **p2 = &p;

a是一个普通变量,而p是一个指针变量,它存放了a的地址,而p2是一个二级指针变量,它存放了p的地址:它们在内存中的关系如下图:

从图中可以看出,二级指针p2保存的是一级指针p的地址。那么三级,四级指针…保存的又是什么地址?指针里保存的肯定是内存地址,但是二级指针只能保存一级指针,对应的三级指针只能保存二级指针,如下例子,如果让三级指针保存一级指针的地址是会报错的。

#include 
using namespace std;

void _test_multileve_pointer() {
    int a = 1;
    int b = 2;
    int* p = &a;
    int** p2 = &p;
    int*** p3 = &p2;
    //如果将一级指针p的地址(即&p)赋值给三级指针*** p3系统是编译不通过的
    //int*** p3 = &p;
    cout << "p:" << *p<< " address:" << p << "\n";
    cout << "p2:" << *p2 << " address:" << p2 << "\n";
    cout << "p3:" << *p3 << " address:" << p3 << "\n";
}

int main()
{
    _test_multileve_pointer();
}

4.指针运算

指针的算术运算仅限于2种形式。第一种形式:指针 ± 整数这种形式只能用于指向数组中某个元素的指针。这类表达式的结果类型也是指针。可以对指针变量 p 进行 p++、p–、p + i (p+i操作并不会真是的移动p指向的内存地址位置,而p++和p—是会真实的移动p指向的内存地址位置)等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i 个操作数。用一张图来说明一下:

在上图中,10000000等是内存地址的十六进制表示(数值是假定的),p 是一个 int 类型的指针,指向内存地址 0x10000008 处。则 p++ 将指向与 p 相邻的下一个内存地址,由于 int 型数据占 4 个字节,因此 p++ 所指的内存地址为 1000000b。其余类推。不过要注意的是,这种运算并不会改变指针变量 p 自身的地址,只是改变了它所指向的地址。

#include

using namespace std;

int main() {
    int arr[10] = {1,23,3,44,5,56,7,8,49,10};
    //如下,数组只有9个数据,这种情况下通过arr[9]或 *(p+9)来访问第10个数据,得到的结果是0,本来以为会报错,但是却没有报错
    //int arr[10] = { 1,23,3,44,5,56,7,8,49};
    int* p = arr;
    for (int i = 0; i

输出结果:

总结(1) ++的优先级比高,所以p++相当于*(p++)(2) 运算符++和–是会真实的移动指针的位置,而+和-只是单纯的获取指针移动向前移动指定地址里的值,并不是真正的移动指针的位置。第二种形式:指针 – 指针只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。举个例子:

void test2() {

    int arr[10] = { 1,23,3,44,5,56,7,8,49,10 };
    int result;
    int* p1 = &arr[2];
    int* p2 = &arr[8];

    result = p2 - p1;
    printf("%d\n", result);// 输出结果为 6
}

总结(1) 指针-指针这种操作只能当两指针指向同一个数组才能使用。(2) 指针-指针的运算结果是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位)。

5.指针与数组

1)指针数组

指针是一个变量,而数组是用于存储变量的容器,因此,指针也可以像其他变量一样存储在数组中,也就是指针数组。 指针数组是一个数组,数组中的每一个元素都是指针。声明一个指针数组的方法如下:

    int *p[10];    // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向int类型的指针

也就是说指针数组说到底还是一个数组,只不过这个数组保存的数据是指针。在上述声明中,由于 [] 的优先级比 * 高,故 p 先与 [] 结合,成为一个数组 p[];再由 int * 指明这是一个 int 类型的指针数组,数组中的元素都是 int 类型的指针。数组的第 i 个元素是 p[i],而 p[i] 是一个指针。由于指针数组中存放着多个指针,操作灵活,在一些需要操作大量数据的程序中使用,可以使程序更灵活快速。


如下代码, arr[Max]是一个长度为3的指针数组,里面存放的都是地址。


void _test_pointerArray() {
    const int MAX = 3;
    int a = 1;
    int b = 2;
    int c = 3;
    int * arr[MAX] = { &a,&b,&c };

    //无法通过int* p[MAX] = arr;这种方式来将arr指针数组赋值给p[MAX]
    //int* p[MAX] = arr;
    int* p[MAX];
    //必须对指针数组里的每个值都赋值,否则,在访问这个指针数组的值的时候,如果该值为未赋值,程序就会报错
    //正确的赋值方式如下
    for (int i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
        p[i] = arr[i];
    }

    for (int i = 0; i < sizeof(p) / sizeof(p[0]); i++) {
        cout << "p:" << *p[i] << " address:" << p[i] << "\n";
        cout << "arr:" << *arr[i] << " address:" << arr[i] << "\n";
    }
}

2)数组指针

数组指针是一个指针,它指向一个数组。声明一个数组指针的方法如下:

    int (*p)[10];        // 声明一个数组指针 p ,该指针指向一个数组

由于 () 的优先级最高,所以 p 是一个指针,指向一个 int 类型的一维数组,这个一维数组的长度是 10,这也是指针 p 的步长。也就是说,执行 p+1 时,p 要跨过 n 个 int 型数据的长度。数组指针与二维数组联系密切,可以用数组指针来指向一个二维数组,如下:

//获取二维数组行列数
//行数 = sizeof(array) / sizeof(array[0]);
//列数 = sizeof(array[0]) / sizeof(array[0][0]);
//数组指针是一个指针,它指向一个数组。
void _test_arrayPointer() {

    int arr[2][3] = { 1,2,3,4,5,6 };
    const int row = sizeof(arr) / sizeof(arr[0]);
    const int col = sizeof(arr[0]) / sizeof(arr[0][0]);
    //(* p)[0]={1,2,3},(* p)[1]={4,5,6}
    int(* p)[3] = arr;

    cout << row << "\n";
    cout << col << "\n";
    for (int i = 0; i < row; i++) {
        for (int j = 0; j < col; j++) {
            cout << "p:" << p[i][j] << " address:" << &p[i][j] << "\n";
            cout << "p:" << p[i][j] << " address:" << p[i]+j << "\n";
        }
    }
}

代码中,(p)[3]是一个指针,( p)[0]={1,2,3},(* p)[1]={4,5,6},也就是说(* p)[0] 和(* p)[1]分别指向二维数组arr[0][0],arr[1][0],(p)[3]中的3并不是指指针p有有三个一维数组,而是指针p包含的数组的长度都是3,至于有多少个一位数组并不能在(p)[3]中体现出来。再看下面的例子:如要将二维数组赋给一指针,应这样赋值,

int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]。
 p++;       //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]。
*(p+1)+1 //表示a[1][1]的地址,*(*(p+1)+1)表示a[1][1]的值。
p[1]+1   //表示a[1][1]的地址,p[1][1]表示a[1][1]的值。

所以数组指针也称指向一维数组的指针,亦称行指针。

6.指针与结构

在描述指针和结构的关系之前先搞清楚什么是结构。

1)定义结构

使用关键值struct定义结构,如下:

struct tag { 
member-list ,
member-list ,
member-list ,
... 
} variable-list ;

tag 是结构体标签,可以理解为类似于int,char的数据类型。member-list 是标准的变量定义,比如 int i; 或者 float f,或者其他有效的变量定义,甚至member-list还可以定义为结构。variable-list 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量,其实就是对变量的声明,当然,你不一定要再定义结构体的时候进行变量的声明,你也可以在调用该结构体之前的地方进行声明。如下,定义了一个名为Student的数据类型,这里就没有在定义结构体的时候声明一个Student的变量:

    struct Student
    {
        char name[10];
        int age;
        char sex;
};

再如下,定义了一个名为Tearcher的数据类型,在定义结构体的同时还声明了一个变量名为teacher的变量:

    struct Teacher
    {
        char name[10];
        int age;
        char sex;
        char object[10];
    }teacher;

当然,你还可以同时初始化该变量。

    struct Teacher
    {
        char name[10];
        int age;
        char sex;
        char object[10];
}teacher = { "Green",28,'F',"Math" };

2)声明结构

你可以在定义结构体的时候,声明结构,如下:

        struct Teacher
        {
            char name[10];
            int age;
            char sex;
            char object[10];
    }teacher;

还有在调用之前声明结构:

        struct Teacher
        {
            char name[10];
            int age;
            char sex;
            char object[10];
        };
Teacher teacher;

只需要把你定义的结构体当作普通的数据类型来声明即可。

3)访问结构体成员

访问结构的成员使用成员访问运算符(.),如下代码

#include

using namespace std;
struct Student
{
    char name[10];
    int age;
    char sex;
};

struct Teacher
{
    char name[10];
    int age;
    char sex;
    char object[10];
};

Teacher teacher;

int main() {

    Student student1 = {"Tom",12,'M'};
    Student student2 = { "Jerry",15,'F' };
    cout << "name:" << student1.name << " age:" << student1.age << " sex:" << student1.sex << "\n";

    cout << "name:" << teacher.name << " age:" << teacher.age << " sex:" << teacher.sex << " object:" << teacher.object<< "\n";
    Teacher teacher2 = { "Gray",23,'M',"English" };
    cout << "name:" << teacher2.name << " age:" << teacher2.age << " sex:" << teacher2.sex << " object:" << teacher2.object << "\n";
}

4)结构指针

结构指针是指向结构的指针。下面是通过指针来访问结构体的数据。

        Teacher teacher2 = { "Gray",23,'M',"English" };
        Teacher* p = &teacher2;
        cout << "name:" << teacher2.name << " age:" << teacher2.age << " sex:" << teacher2.sex << " object:" << teacher2.object << "\n";
cout << "name:" << p->name << " age:" << p->age << " sex:" << p->sex << " object:" << p->object << "\n";

7.指针与函数

C语言的所有参数均是以“传值调用”的方式进行传递的,这意味着函数将获得参数值的一份拷贝。这样,函数可以放心修改这个拷贝值,而不必担心会修改调用程序实际传递给它的参数。

1)函数里传递指针

问题一:写一个函数,交换两个参数中的值如下代码,_exchange函数并不能实现对两个参数的值进行交换,这是因为它只是对a和b的值传递给_exchange函数的x和y,所以_exchange函数里只是对x和y的值进行了操作,并不是对a和b进行交换操作。_exchange2函数也一样,也只是进行了值传递,然后在函数里对x和y的内存地址进行操作,并不是对a和b进行操作。_exchange3函数传进来的是a和b的地址,所以可以直接修改a和b存放的值进行交换。

#include 

using namespace std;
void _test_address()
{
    int temp = 1;
    int* p = &temp;
    cout << "temp address: "<<&temp << "\n";
    cout << "p address: " << &p << "\n";
    cout << "*p address: " << *p << "\n";
    cout << "&*p address: " << &*p << "\n";
}

//x和y的声明周期在这个函数执行完后就结束了,且x和y是形参,这种赋值属于浅拷贝,也就是将传递过来的值复制,而不是将x,y指向传递过来的值//也就是说,x和y指向的值和传来过来的参数指向的值不是指向同一个内存地址,所有你改变x和y的值并不会影响传过来的参数

void _exchange(int x, int y)
{
    int p = x;
    x = y;
    y = p;
}

//如果想通过下面这种方式来改变传过来的参数的指向,是不可能实现的
//因为当传过来的参数传给_exchange2后,只是将值传递过来而已,所有下面的操作也只是对x和y的内存地址进行操作
//而不是对传过来的参数的地址进行操作
void _exchange2(int x, int y)
{
    int *p = &x;
    int *p2 = &y;
    int temp = *p;
    *p = *p2;
    *p2 = temp;
}

//这个方法传递过来的是两个内存地址,
void _exchange3(int *x, int *y)
{
    int temp = *y;
    *y = *x;
    *x = temp;
}
int main()
{
    int  var = 20;   // 实际变量的声明
    int* ip;        // 1.指针变量的声明

    ip = &var;       // 2.在指针变量中存储 var 的地址

    cout << "Value of var variable: ";
    cout << var << endl;

    // 输出在指针变量中存储的地址
    cout << "Address stored in ip variable: ";
    cout << ip << endl;

    // 3.访问指针中地址的值
    cout << "Value of *ip variable: ";
    cout << *ip << endl;

    _test_address();

    int a=3;
    int b = 4;
    cout << "\nstart a: " << a;
    cout << "\n address a: " << &a;
    cout << "\n b: " << b;
    cout << "\n address b: " << &b << "\n";
    _exchange(a,b);
    cout << "\n _exchange(a,b)======================";
    cout << "\n a: " << a;
    cout << "\n address a: " << &a;
    cout << "\n b: " << b;
    cout << "\n address b: " << &b << "\n";
    _exchange2(a, b);
    cout << "\n _exchange2(a,b)======================";
    cout << "\n a: " << a;
    cout << "\n address a: " << &a;
    cout << "\n b: " << b;
    cout << "\n address b: " << &b << "\n";
    _exchange3(&a, &b);
    //swap(a,b);
    cout << "\n _exchange3(&a, &b)======================";
    cout << "\n a: " << a;
    cout << "\n address a: " << &a;
    cout << "\n b: " << b;
    cout << "\n address b: " << &b << "\n";
    return 0;
}

输出结果:

2)指向函数的指针

在C语言中,函数本身不是变量,但是可以定义指向函数的指针,也称作函数指针,函数指针指向函数的入口地址。函数指针是指向函数的指针变量。通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。函数指针可以像一般函数一样,用于调用函数、传递参数。函数指针变量的声明:

//返回值类型 (* 指针变量名)([形参列表]);
int (*pointer)(int*, int*);        // 声明一个函数指针

下面通过例子来了解函数指针

#include

using namespace std;

int operation(char type,int x,int y);
int add(int x,int y);
int sub(int x, int y);
int main() {
    char a;
    int b;
    int c;
    int (*p)(char, int, int) = &operation;//&可以省略
    cout << ("请输入操作类型:");
    cin>>a;
    cout << ("请输入第一个值:");
    cin >> b;
    cout<<("请输入第二个值:");
    cin >> c;
    int result = p(a, b, c);
    cout<<"结果:"<

int (*p)(char,int,int) = operation函数指针p指向了operation这个函数,调用p(a,b,c)的时候,就相当于调用了函数operation(a,b,c)输入结果:

8.参考文章

指针的基本知识:
https://www.runoob.com/cplusplus/cpp-pointers.html多级指针:
https://blog.csdn.net/walle2018/article/details/80716925

原文链接:,转发请注明来源!