zoukankan      html  css  js  c++  java
  • C++指针

    指针

    C++ 中内存单元内容与地址

    内存由很多内存单元组成,这些内存单元用于存放各种类型的数据。

    计算机对内存的每个内存单元都进行了编号,这个编号就称为内存地址,地址决定了内存单元在内存中的位置。

    记住这些内存单元地址不方便,于是C++语言的编译器让我们通过名字来访问这些内存位置。

    举例

     如果使用地址那我们编程会很不方便,使用变量名代替地址

     指针的定义和间接访问操作

    指针定义的基本形式:指针就是一个变量,其符合变量定义的基本形式,它存储的是值的地址。对类型T,T*是“到T的指针”类型,一个类型为T*的变量能保存一个类型T的对象的地址

    int a = 112; float c = 3.14;
    int* d = &a; float* e = &c; // 指针变量保存的是地址,使用取地址符&

    通过一个指针访问它所指向地址的过程称为间接访问(indirection)或者引用指针(dereferencing the point)

    这个用于执行间接访问的操作符是单目操作符*

    cout << (*d) << endl;  cout << (*e) << endl;

    一个变量有三重要信息:

    • 变量的地址位置
    • 变量所存的信息
    • 变量的类型

    指针变量也是变量,其特殊之处在于所存的信息是地址和类型

    指针变量是一个专门用来记录变量的地址的变量,通过指针变量可以间接访问另一个变量的值

    C++的原生指针

    数组与指针

    数组名对应一个地址空间

    int main()
    {
        char aStr[] = {"helloworld"};
        char aStr1[] = "helloworld" ; // 同aStr1
    
        char* pStr = (char*)"helloworld";
        
    
        pStr = aStr; // 指针变量的值允许改变
        // aStr = pStr; // 数组变量的值不允许改变
        aStr[0] = 'H'; // aStr[index]的值可以改变
    
    }

    pStr[index]的值可变不可变,取决于所指区间的存储区域是否可变。

    左值与右值

    概念:

    一般说法,编译器为其单独分配了一块存储空间,可以取其地址的,左值可以放在赋值运算符左边

    右值指的是数据本身,不能取到其自身地址,右值只能赋值运算右边。

    左值最常见的情况如函数和数据成员的名字

    右值是没有标识符、不可以取地址的表达式,一般也称之为“临时对象”。

    比如:a=b+c

    &a是允许的操作,而&(b+c)不能通过编译,因此a是一个左值,而(b+c)是一个右值。

     一般类型指针T*

    T泛指任何一种类型

    *的两种意义:

    • 变量声明中使用,表示该变量是一个储存地址的变量,即指针
    • 在表达式中使用,表示取该地址对应的内存单元的值

    指针的数组

    array of pointers

    一组指针,数组的元素是指针

    T* t[]

    可看成

    T* (t[])

    数组的指针

    a pointer to an array

    指针指向一个数组

    T(*T)[]
    int main()
    {
        // 4字节的最小的整数,-1,0,4字节最大的整数
        int c[4] = {0x80000000, 0xFFFFFFF, 0x00000000, 0x7FFFFFFF};
        int* a[4]; // 指针的数组 存的是指针
        int(*b)[4]; // 数组的指针
        b = &c; // b指向数组c
        for (unsigned int i = 0; i < 4; i++)
        {
            a[i] = &(c[i]); // 将每个元素的地址赋值给指针的数组中的元素,即指针
        }
        cout << *(a[0]) << endl; // -2147483648
        cout << (*b)[3] << endl; // 2147483647 (*b)取出数组,然后[3]对数组进行访问
    }

    const与指针

    • const pointer
    • pointer to const

    const修饰

    • 看左侧最近的部分
    • 左侧没有,则看右侧
    int main()
    {
        char strH[] = { "helloworld" };
        char const* pStr1 = "helloworld"; // 左侧最近是char,修饰char,pStr1指针指向的地址可变,但是该地址下字符值不可变
        const char* pStr11 = "helloworld"; // 左侧没有看右侧,等价于pStr1
        char* const pStr2 = (char*)"helloworld"; // 左侧最近是*,修饰指针,pStr2一旦指向某个地址,就不能再指向其他地址,该指向就不许变
        char const* const pStr3 = "helloworld"; // 指向和所指向的值都不可变
        pStr1 = strH;
        // pStr2 = strH; // pStr2不可修改
        // pStr3 = strH; // pStr3不可修改
    }

    指向指针的指针

    int main()
    {
        int a = 123;
        int* b = &a;
        int** c = &b;
    }

     *操作符具有从右向左结合性
    
    **这个表达式相当于*(*c),必须从里向外逐层求值
    
    *c得到的是c指向的位置,即b
    
    **c相当于*b,得到变量a的值

    野指针和空指针

    未初始化和非法的指针

    int main()
    {
        int *a; // a指向哪里?发送什么?
        *a = 12;
    }

    运气好的话,定位到一个非法地址,程序会出错,从而终止

    最坏的情况,定位到一个可以访问的地址,无意修改了它,这样的错误难以捕捉,引发的错误可能与原先用于操作的代码毫不相干,无法跟踪。

    指针一定要初始化并被恰当赋值。

    Null指针

    一个特殊的指针变量,表示不指向任何东西。

    int* a = NULL;

    NULL指针的概念非常有用,它给了一种方法,来表示特定的指针目前未指向任何东西

    注意事项:

    对于一个指针,如果已经知道将被初始化为什么地址,那么请赋给它这个地址,否则请把它设为NULL。在对一个指针进行间接引用前,请先判断这个指针的值是否为NULL。

    int main()
    {
        int a = 123;
        int* p = NULL;
        p = &a;
        if (p != NULL)
        {
            cout << *p << endl;
        }LL
        p = NULL; // 不用时置为NULL
    
        return 0;
    }

    杜绝野指针

    指向“垃圾”内存的指针,if等判断对它们不起作用,因为没有置NULL

    一般有三种情况:

    • 指针变量没有初始化
    • 已经释放不用的指针没有置为NULL,如delete和free之后的指针
    • 指针操作超越了变量的作用范围

    指针使用注意事项:

    • 没有初始化的,不用的或超出范围的指针请置为NULL。

    指针基本运算

    &与*操作符

    char ch = 'a'; char* cP = &ch;

     &ch取地址,会将ch的地址取出来,放到一个空间中(不是cp变量),我们不知道该空间地址,不能使用&,不能作为左值,&ch可以取到地址,但是存储这个地址的空间我们得不到,只能作为右值。

    char ch = 'a';
    char* cP = &ch;

    int main()
    {
        char ch = 'a';
    
        // &
        // &ch = 97; // &ch左值不合法
        char* cp = &ch; // &ch右值
        // &cp = 97; // &cp左值不合法
        char** cpp = &cp; // &cp右值
    
        // *
        *cp = 'a'; // *cp左值取变量ch位置
        char ch2 = *cp; // *cp右值取变量ch存储的值
        // *cp + 1 = 'a'; // *cp + 1 左值不合法的位置
        ch2 = *cp + 1; // *cp + 1 右值取到字符做ASCII码+1操作
        *(cp + 1) = 'a'; // *(cp+1)左值语法上合肥,取ch后面位置
        ch2 = *(cp + 1); // *(cp + 1)右值语法上合法,取ch后面位置的值
        return 0;
    }

    ++与--操作符

     

     

    关于++++,----等

     

    数组名和指针

    揭密数组名 https://www.cnblogs.com/fenghuan/p/4775023.html
    现在到揭露数组名本质的时候了,先给出三个结论:
    (1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;
    (2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量;
    (3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!
    1、数组名指代一种数据结构:数组
    现在可以解释为什么第1个程序的这一行:sizeof(str);的输出为10的问题,根据结论1,数组名str的内涵为一种数据结构,即一个长度为10的char型数组,所以sizeof(str)的结果为这个数据结构占据的内存大小:10字节。
    再看:

    1.int intArray[10];
    2. cout << sizeof(intArray) ;

    第2行的输出结果为40(整型数组占据的内存空间大小)。
    如果C/C++程序可以这样写:

    1. int[10] intArray;
    2. cout << sizeof(intArray) ;

    我们就都明白了,intArray定义为int[10]这种数据结构的一个实例,可惜啊,C/C++目前并不支持这种定义方式。
    2、数组名可作为指针常量
    根据结论2,数组名可以转换为指向其指代实体的指针,所以程序1中的第5行数组名直接赋值给指针,程序2第7行直接将数组名作为指针形参都可成立。
    下面的程序成立吗?

    1. int intArray[10];
    2. intArray++;

    读者可以编译之,发现编译出错。原因在于,虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改。 
    而指针,不管是指向结构体、数组还是基本数据类型的指针,都不包含原始数据结构的内涵,在WIN32平台下,sizeof操作的结果都是4。
    顺 便纠正一下许多程序员的另一个误解。许多程序员以为sizeof是一个函数,而实际上,它是一个操作符,不过其使用方式看起来的确太像一个函数了。语句 sizeof(int)就可以说明sizeof的确不是一个函数,因为函数接纳形参(一个变量),世界上没有一个C/C++函数接纳一个数据类型(如 int)为"形参"。
    3、数组名可能失去其数据结构内涵 
    到这里似乎数组名魔幻问题已经宣告圆满解决,但是平静的湖面上却再次掀起波浪。请看下面一段程序:

    #include <iostream>
    using std::cout;
    using std::endl;
    void arrayTest(char str[])
    {
         cout << "sizeof(str):" << sizeof(str) << endl;
    }
    int main()
    {
        char str1[10] = "I love U.";
        arrayTest(str1);
        
        system("pause");
        return 0;
    }

    程序的输出结果为:


    一个可怕的数字,前面已经提到其为指针的长度!

    结论1指出,数组名内涵为数组这种数据结构,在arrayTest函数体内,str1是数组名,那为什么sizeof的结果却是指针的长度?这是因为:

    (1)数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;

    (2)很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

    所以,数组名作为函数形参时,其全面沦落为一个普通指针!它的贵族身份被剥夺,成了一个地地道道的只拥有4个字节的平民。

    以上就是结论4。

    指针和引用

    指针对于一个类型T,T*就是指向T的指针类型,也即一个T*类型的变量能够保存一个T对象的地址,而类型T是可以加一些限定词的,如const、volatile等等。见下图,所示指针的含义

    char c = 'a';
    
    char *p = &c; //p里面存放的是c的地址

    引用是一个对象的别名,主要用于函数参数和返回值类型,符号X&表示X类型的引用。见下图,所示引用的含义:

    int i=1;
    
    int &r = i;    //此时i=r=1;

    若执行r=2;//此时i=r=2;

    int *p = &r;  //p指向r;

    指针和引用的区别

    1.首先,引用不可以为空,但指针可以为空。前面也说过了引用是对象的别名,引用为空——对象都不存在,怎么可能有别名!故定义一个引用的时候,必须初始化。因此如果你有一个变量是用于指向另一个对象,但是它可能为空,这时你应该使用指针;如果变量总是指向一个对象,i.e.,你的设计不允许变量为空,这时你应该使用引用。如下图中,如果定义一个引用变量,不初始化的话连编译都通不过(编译时错误)

    而声明指针是可以不指向任何对象,也正是因为这个原因,使用指针之前必须做判空操作,而引用就不必。

    2.其次,引用不可以改变指向,对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。

    3.再次,引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,4个字节

    4.最后,引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const 指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针(即多个指针指向一块内存,free掉一个指针之后,别的指针就成了野指针)

    总之,用一句话归纳为就是:指针指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名,引用不改变指向。



  • 相关阅读:
    记Spring搭建功能完整的个人博客「Oyster」全过程[其二] Idea中Maven+SpringBoot多模块项目开发的设计和各种坑(模块间依赖和打包问题)
    记Spring搭建功能完整的个人博客「Oyster」全过程[其一] 整体思路:需求、架构及技术要求
    [总结-动态规划]经典DP状态设定和转移方程
    HDU-6217 BBP Formula 脑洞
    UVA-11426 GCD
    UVA-11806 Cheerleaders 计数问题 容斥定理
    CodeForces-546D Soldier and Number Game 筛法+动态规划
    CodeForces-148D Bag of mice 概率dp
    Gym-101615D Rainbow Roads 树的DFS序 差分数组
    AVL树 算法思想与代码实现
  • 原文地址:https://www.cnblogs.com/aidata/p/13023157.html
Copyright © 2011-2022 走看看