美文网首页
(十二)QT专题-C++指针

(十二)QT专题-C++指针

作者: GoodTekken | 来源:发表于2023-02-19 13:56 被阅读0次

    在C++中,指针(pointer)就是一个可以存储对象的内存地址的变量(而不是直接存储这个对象)。Java和C#都有类似的概念--“引用”(reference),但是在语法上却不相同。我们从研究一个精心设计的例子开始。利用它来说明指针的用法:

    #include "point2d.h"
    int main()
    {
      Point2D alpha;
      Point2D beta;
      Point2D *ptr;
      
     ptr = α
     ptr -> setX(1.0);
     ptr -> setY(2.5);
     
     ptr = β
     ptr -> setX(4.0);
     ptr ->setY(4.5);
    
     ptr = 0;
     return 0;
    }
    

    这个例子依赖于前一小节给出的Point2D类。第4行和第5行定义了俩个Point2D对象(十一)QT专题-C++类定义。根据Point2D的默认构造函数,这两个对象被初始化为(0,0)。
    第6行定义了一个指向Point2D对象的指针。指针的语法是在变量名的前面再加上一个星号。由于没有初始化这个指针,所以它包含的是一个随机的内存地址值。通过在第7行给这个指针分配alpha对象的地址就可以解决这个初始化问题。这里的一元运算符“&”可以返回一个对象的内存地址值。地址值通常是一个32位或者是64位的整数型,可以用来确定一个对象在内存中的偏移量。
    在第8行和第9行,我们通过ptr指针访问alpha对象。因为ptr是指针而不是对象,所以必须使用“->”(箭头)操作符代替“.”(点)操作符。
    在第10行,我们把beta的地址也赋给这个指针。于是从此时开始,通过这个指针执行的任何操作都会影响到beta对象。
    第13行把这个指针设置为(null)指针。C++没有一个可以用于表示不指向对象指针的关键字。所以,我们改换用值0(或者是符号常量NULL,它可以扩展为0)来代替。试图使用一个空指针会造成系统的崩溃,其提示的错误信息有“段错误”(Segmentation fault)、“常规保护错误”(General protection fault)或者是“总线错误”(Bus error)等。使用程序调试器,可以找出是哪一行代码造成了系统的崩溃。
    在这个函数的最后,alpha对象保存了坐标对(1.0,2.5),而beta保存了(4.0,4.5)。
    指针通常用于存储使用使用new动态分配的对象。在C++术语中,我们把这样的对象称为是分配在“堆”(heap)上,而局部变量(在一个函数中定义的变量)则存储在“栈”(stack)里。
    这里给出了一段用来说明使用new进行动态内存分配的代码片段:

    #include "point2d.h"
    int main()
    {
      Point2D *point = new Point2D;
     point->setX(1.0);
     point->setY(2.5);
    
     delete point;
     return 0;
    }
    

    new 操作符返回一个新近分配对象的内存地址。我们把这个地址存储在一个指针变量中,并且通过这指针访问该对象。当处理完这个对象后,就可以使用delete操作符释放它的内存。不像Java和C#,C++没有垃圾信息收集器,当不再需要那些动态分配的对象时,就必须明确使用delete来释放它们。利用第2章中讲述的Qt父-子对象机制,可以大大简化C++程序中的内存管理工作。
    如果忘记调用delete,则内存就会一直保留到该程序结束时为止。这在上面的例子中不是什么大问题,因为我们只是分配了一个对象,但是如果在一个总是需要不断分配新对象的程序中,就可能造成程序总是不断分配内存,那么就可能将机器的内存耗尽。对象一旦删除,则指向该对象的指针变量仍旧会保存这个对象的地址值。这样的指针就称为“悬摆指针”(dangling pointer),最好不要再使用这样的指针访问该对象。Qt提供了一种“智能”(smart)指针QPoint<T>,如果删除了它所指向的QObject对象,那么它就会自动把自己设置为0。
    在上面的例子中,我们调用了默认的构造函数并且调用setX()和setY()来初始化该对象。我们本应当使用带两个参数的构造函数来替代默认的构造函数:

    Point2D *point = new Point2D(1.0, 2.5);
    

    这个例子并不需要使用new和delete。我们最好也像下面那样在栈上分配该对象:

    Point2D point;
    point.setX(1.0);
    point setY(2.5);4.0
    

    像这样分配的对象会在出现它们的程序块的末尾自动得到释放。
    如果不打算通过该指针来修改这个对象,则可以把指针声明为const型指针。例如:

    const Point2D *ptr = new Point2D(1.0, 2.5);
    double x = ptr ->x();
    double y = ptr ->y();
    
    //WON'T COMPILE
    ptr ->setX(4.0);
    *ptr = Point2D(4.0, 4.5);
    

    这个常量指针ptr只能用于调用常量成员函数,比如x()和y()。当不打算使用指针修改它们时,把指针声明为const是一种不错的习惯。而且,如果该对象自身就是常量,那么我们就没有什么选择了,只能使用常量指针来存储它的地址值。const的用法可以为编译器提供一定的信息,这可以提早发现一些bug,并且也可以获得良好的性能。C#有const关键字,与C++的const关键字相似。而在Java中,最为接近的等价概念就是final了,但是它只能保护变量不被赋值,从而不能避免不在它上面调用“非常量”的成员函数。
    指针既可以用在内置类型中,也可以用在类上。需要说明的是,一元运算符“*”可以返回与这个指针相关的对象的值。例如:

    int i = 10;
    int j = 20;
    
    int *p = &i;
    int *q = &j;
    
    std::cout << *p << " equals 10" << std::endl;
    std::cout << *q << " equals 20" << std::endl;
    
    *p =40;
    std::cout << i << " equals 40" << std::endl;
    
    p=q;
    *p = 100;
    std::cout << i << " equals 40" << std::endl;
    std::cout << j << " equals 100" << std::endl;
    

    箭头运算符“ -> ”可用于通过指针来访问对象的成员,这纯粹是一种语法甜头(syntactic sugar)而已。除了ptr -> member的形式之外,我们还可以使用(ptr).member的形式。这里的圆括号是必须的,因为“.”运算符具有比“”运算符更高的运算优先级。

    指针在C和C++中名声不良,正是因为这一点,Java经常鼓吹自己没有指针而大做文章。实际上,C++指针在概念上与Java和C#中的引用非常相似,只是我们还可以使用指针来遍历整个内存而已 —— 关于这一点,在这一节的后面还会讲到。此外,在Qt中还包含了“写时复制”(copy on write)的容器类,它具有与C++一样的可在栈上实例化任意类的能力,这就意味着通常可以尽量避免指针的使用。

    相关文章

      网友评论

          本文标题:(十二)QT专题-C++指针

          本文链接:https://www.haomeiwen.com/subject/pgeikdtx.html