常(const)+ 对象 + 指针:玻璃罩到底保护哪一个

2024-03-03 11:48

本文主要是介绍常(const)+ 对象 + 指针:玻璃罩到底保护哪一个,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

原创案例讲解——”玻璃罩const”系列的三篇文章:

1. 使用常对象——为共用数据加装一个名为const的玻璃罩

2. 常(const)+ 对象 + 指针:玻璃罩到底保护哪一个

3. 对象更有用的玻璃罩——常引用


  在上一篇文章《使用常对象——为共用数据加装一个名为const的玻璃罩》中,利用案例讨论了运用常对象,常成员函数、常数据成员及其用法。const这个玻璃罩让数据只能看,不能改,有效地避免程序免受不该出现的修改(引起bug的元凶)操作的影响。

  本文继续讨论const这个玻璃罩,主要是要引入指针来。这时,玻璃罩要保护何方?事情似乎变得更复杂。但实际情况是,C++的内在机理仍然很清晰,我们还是要借助实例看下去。读者如果一边能够将程序放到自己的IDE中调一调,改一改,撞撞错,那是更好的了。


  一、指向对象的常指针

  定义指向对象的常指针的一般形式为:

    类名 * const 指针变量名;

  首先应该正确地将这一定义形式识别准确了。按照结合的顺序(类名 (* (const 指针变量名))):就是首先是常量;其次是指针;最后确定指向的是类。作为常量的定义,还要注意的是定义时必须初始化。

  例如,下面程序中第23行:Test * const p1 = &t1;:p1是常量,是指针,指向Test类的对象,初始化为t1对象的地址。看惯了,挺可爱的。

//程序1
#include <iostream>  
using namespace std;  
class  Test  
{  
private:   int x;   int y;  
public:  Test(int a, int b){x=a;y=b;}  void printxy() const;  void setX(int n) {x=n;}  void setY(int n) {y=n;}  
} ;  
void Test::printxy() const   
{  cout<<"x*y="<<x*y<<endl;  
}  
void main(void)  
{      Test t1(3,5),t2(4,7);const Test t3(5,9); Test * const p1 = &t1;//p1 = &t2; //招致错误——error C3892: “p1”: 不能给常量赋值p1->setX(5);t1.printxy( );   //输出x*y=25Test *p2=&t1;p2=&t2;p2->setX(5);p2->printxy();  //输出x*y=35//p2=&t3;  //招致错误——error C2440: “=”: 无法从“const Test *”转换为“Test *”//p2->setX(7);//p2->printxy();  system("pause");  
}  

  常指针一经初始化后将不能再被改变值(这是常之所在)。但指针指向的值是否可变,取决于指向的对象。例如第25行 p1->setX(5); 成功地修改了t1对象中的 x 成员的值。

  程序中p2不是常指针,所以在第29行,可以为其赋值为&t2,从而指向了t2对象。但是在第33行的赋值却会发生错误。错误的原因不是p2的值(指针)不可变,而是t3是一个常对象,需要指向常对象的指针变量进行处理。


  二、指向常对象的指针变量

  定义指向常变量的指针变量的一般形式为

    const 类名 *指针变量名;

  识别指向常对象的指针要这样看。按照结合的顺序(const ( 类名( *指针变量名))):就是首先是指针变量;其次指向的是类的对象;最后,这个对象应该是常对象。

  例如,下面程序中第22行:const Test *p1;:p1是指针,指向test类的对象,指向的是Test类的常对象,初始化为t1对象的地址。

//程序2
#include <iostream>  
using namespace std;  
class  Test  
{  
private:   int x;   int y;  
public:  Test(int a, int b){x=a;y=b;}  void printxy() const;  void setX(int n) {x=n;}  void setY(int n) {y=n;}  
} ;  
void Test::printxy() const   
{  cout<<"x*y="<<x*y<<endl;  
}  
void main(void)  
{      const Test t1(3,5),t2(4,7);const Test *p1;p1 = &t1;//p1->setX(5); //招致错误——error C2662: “Test::setX”: 不能将“this”指针从“const Test”转换为“Test &”t1.printxy( );   //输出x*y=25p1=&t2;p1->printxy();  //输出x*y=35Test t3(1,3);p1=&t3;t3.setX(2);//p1->setX(3);//招致错误——error C2662: “Test::setX”: 不能将“this”指针从“const Test”转换为“Test &”p1->printxy(); //输出x*y=6Test const *p3=&t1;//p3->setX(9);//招致错误——error C2662: “Test::setX”: 不能将“this”指针从“const Test”转换为“Test &”p3->printxy();  system("pause");  
} 

  在程序中可以看出,第24行,由于p1指向的是常对象,是不能被修改的对象,p1->setX(5);试图修改对象的数据成员,带来错误并不意外。

  指向常对象的指针变量,是对象不能变,不能通过指针改变其值,而不是指针不能变,所以在第27行,p1=&t2;使p1指向了另外一个常对象。

  如果一个变量已被声明为常变量,只能用指向常变量的指针变量指向它,而不能用指向非const型变量的指针变量去指向它。

  第31行将指向常对象的指针指向了一个非const对象,这是允许的。第32行可以成功地修改这一非const对象的值,但是不要指望p1(指向常对象的指针)去修改。在对待修改的这件事情上,C++采取的是一种较严格的要求,对象本身是否为“常”和指针指向的对象是否为常,两者中有一个为“常”,就不要修改。

  在第36行,定义的指向对象的常指针p3赋初值为&t1,是一个常对象的地址。(注:去掉下面的部分,表述错误。谢谢2楼的评论。原内容:按照这一定义要表达的内容,似乎p3称为指向常对象的常指针更全面,即*p3=&t1;。第36行成功地通过了编译告诉我们,指向对象的常指针可以指向一个常对象。第37行对修改的禁止由于t1为常对象所致,看来,指向对象的常指针不能改变其指向的常对象。)


  三、用(常)指针作形参:实参如何搭配?

  在上面的例子中,指针是通过赋值直接取得值的。在程序设计中还有另一种很重要的情形,来传递变量的值,那就是函数中的参数传递:将实际参数的值传递给形式参数。在这个传递的过程中,道理一样,下表将形、实参是否为const(常)的4种组合产生的效果进行一个罗列:

序号形参实参是否合法是否可以改变指向的对象的值
(1)指向非const型变量的指针非const变量的地址合法可以
(2)指向非const型变量的指针const变量的地址非法不必讨论
(3)指向const型变量的指针 非const变量的地址合法不可
(4)指向const型变量的指针const变量的地址 合法不可
  在这儿的表述中,用了变量,而不是对象。实际上这两者的本质是统一的,道理一样。这样写作,也是提醒读者不要将二者看成不同的事物。

  先给出程序来:

//程序3
#include <iostream>  
using namespace std;  
class  Test  
{  
private:   int x;   int y;  
public:  Test(int a, int b){x=a;y=b;}  void printxy() const;  void setX(int n) {x=n;}  void setY(int n) {y=n;}  
} ;  
void Test::printxy() const   
{  cout<<"x*y="<<x*y<<endl;  
}  void doSomething(Test *p1)  //(1)形参是指向非const型变量的指针 
{p1->setX(5); p1->printxy( ); 
}void main(void)  
{      Test t1(3,5);doSomething(&t1); //(1)实参是非const变量的地址 system("pause");  
} 
  (1)形参是指向非const型变量的指针,实参是非const变量的地址 

  上面的程序中没有给变量/对象做出任何的限制,调用合法,也能够实施修改操作。


  (2)形参是指向非const型变量的指针,实参是const变量的地址 

  下面的程序将不再给出Test类的定义。这组示例中,区别仅在于函数doSomething()的定义形式和调用中使用的实参。

void doSomething(Test *p1)  //(2)形参是指向非const型变量的指针 
{p1->setX(5);   //由于第XXx行的错误,这一行可能引起的问题尚无机会讨论p1->printxy( ); 
}void main(void)  
{      const Test t1(3,5);doSomething(&t1); //(2)实参是const变量的地址 //这一行招致错误——error C2664: “doSomething”: 不能将参数 1 从“const Test *”转换为“Test *”system("pause");  
} 
  这段程序将出现编译错误。

  错误产生在第10行—— error C2664: “doSomething”: 不能将参数 1 从“const Test *”转换为“Test *”,还提示—— 转换丢失限定符。

  与前面所讲一致,不能将const对象地址赋值给一个非const指针,如果这个操作成功,就会产生严重的后果:doSomething()函数中将能够修改由传递地址值而对应的对象的值。调用函数时,实际参数的值传递给形式参数时,系统会进行自动类型转换,但这个转换无法进行下去,因为“转换丢失限定符”。


  (3)形参是指向const型变量的指针,实参是const变量的地址 

void doSomething(const Test *p1)  //(3)形参是指向const型变量的指针 
{p1->setX(5); //招致错误——error C2662: “Test::setX”: 不能将“this”指针从“const Test”转换为“Test &”p1->printxy( ); 
}void main(void)  
{      const Test t1(3,5);doSomething(&t1); //(3)实参是const变量的地址 system("pause");  
} 
  第3行 p1->setX(5);出错,是因为仅形参p1就限定p1所指向的对象为常对象,是不能被修改的。

  从另一方面讲,第10行的调用doSomething(&t1);“门当户对”,是合法语法规定的。实际参数本身也决定了,对象是不能被改变的。


  (4)形参是指向const型变量的指针,实参是非const变量的地址 

void doSomething(const Test *p1)  //(4)形参是指向const型变量的指针 
{p1->setX(5); //招致错误——error C2662: “Test::setX”: 不能将“this”指针从“const Test”转换为“Test &”p1->printxy( ); 
}void main(void)  
{      Test t1(3,5);doSomething(&t1); //(4)实参是const变量的地址 system("pause");  
} 
  程序出的错误是一模一样的,形式参数p1所指向的对象不能被修改。和(3)稍有不同的是,单从实参的性质来看,p1所指向的对象t1是允许修改的,只是单纯因为形参上做的限定,不能改了。这要遗憾,这是const最大的功绩!若在某一个函数中,需求提及该函数只读取而不修改形参所指的对象,最好的方法就是,将形参设为const型变量的指针,无论实参是const对象,还是非const对象,统统一不能修改,这不正是我们要谈的关于数据的保护吗?


  四、小结

  引入指针之后,让这一部内容马上显得弯弯绕了。这可不是为了绕概念而设置的,最根本的目的,还是实施数据保护。通过将某些指针设置为指向常对象的指针,从而避免利用指针给对象改变值。

  所以,本讲最有价值的地方在于第三部分之(3)和(4)——使用指向常对象的指针做形式参数。根据用指针做形参的机制,在函数中,可以通过指针改变实际参数所给定内存单元的值。如果这部分值是不能被改变的,为了实现这个需求,将指针设为指向常对象的指针,可以让编译器替我们把关。如果开发的是类库,那也可以避免使用者陷入不种令人抓狂的bug阵中。

  另外,在程序执行中,如果一个指针所指向的位置一经初始化就不能再变,指向对象的常指针是最好的选择。

  在C++提供的如此多的机制中,择其最合适的使用,考验的是程序员的智慧。深入理解,灵活搭配,方显专业功底。


(全文完)

这篇关于常(const)+ 对象 + 指针:玻璃罩到底保护哪一个的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/769525

相关文章

JSON字符串转成java的Map对象详细步骤

《JSON字符串转成java的Map对象详细步骤》:本文主要介绍如何将JSON字符串转换为Java对象的步骤,包括定义Element类、使用Jackson库解析JSON和添加依赖,文中通过代码介绍... 目录步骤 1: 定义 Element 类步骤 2: 使用 Jackson 库解析 jsON步骤 3: 添

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

java中VO PO DTO POJO BO DO对象的应用场景及使用方式

《java中VOPODTOPOJOBODO对象的应用场景及使用方式》文章介绍了Java开发中常用的几种对象类型及其应用场景,包括VO、PO、DTO、POJO、BO和DO等,并通过示例说明了它... 目录Java中VO PO DTO POJO BO DO对象的应用VO (View Object) - 视图对象

vue如何监听对象或者数组某个属性的变化详解

《vue如何监听对象或者数组某个属性的变化详解》这篇文章主要给大家介绍了关于vue如何监听对象或者数组某个属性的变化,在Vue.js中可以通过watch监听属性变化并动态修改其他属性的值,watch通... 目录前言用watch监听深度监听使用计算属性watch和计算属性的区别在vue 3中使用watchE

Java将时间戳转换为Date对象的方法小结

《Java将时间戳转换为Date对象的方法小结》在Java编程中,处理日期和时间是一个常见需求,特别是在处理网络通信或者数据库操作时,本文主要为大家整理了Java中将时间戳转换为Date对象的方法... 目录1. 理解时间戳2. Date 类的构造函数3. 转换示例4. 处理可能的异常5. 考虑时区问题6.

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

【C++学习笔记 20】C++中的智能指针

智能指针的功能 在上一篇笔记提到了在栈和堆上创建变量的区别,使用new关键字创建变量时,需要搭配delete关键字销毁变量。而智能指针的作用就是调用new分配内存时,不必自己去调用delete,甚至不用调用new。 智能指针实际上就是对原始指针的包装。 unique_ptr 最简单的智能指针,是一种作用域指针,意思是当指针超出该作用域时,会自动调用delete。它名为unique的原因是这个

Java第二阶段---09类和对象---第三节 构造方法

第三节 构造方法 1.概念 构造方法是一种特殊的方法,主要用于创建对象以及完成对象的属性初始化操作。构造方法不能被对象调用。 2.语法 //[]中内容可有可无 访问修饰符 类名([参数列表]){ } 3.示例 public class Car {     //车特征(属性)     public String name;//车名   可以直接拿来用 说明它有初始值     pu

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非