概述

本文深入介绍了与指针和数组相关的运算符优先级,利用代码示例展示了当左结合和右结合运算符同时存在时的结合方式,同时也演示了如何使用()来强制人为指定结合顺序。

指针、数组相关的运算符优先级

下表展示了相关运算符的优先级,有4个级别,同级别内的运算符按照结合性依次调用。这4类也是所有运算符中优先级最高的4档,其它的运算符优先级都比它们低:

优先级 运算符 描述 结合性
1 :: 作用域解析 左结合
2 ()
[]
.
->
强制运算结合,函数形参列表
数组元素下标访问
类的成员访问
类指针的成员访问
右结合
3 (int)
*
&
强制转换
指针解引用
变量取地址
左结合
4 .*
->*
类的成员指针
类指针的成员指针
左结合

容易产生困惑的、需要仔细进行优先级判断的往往是一个左结合加一个右结合,例如:

  • *ptr[]
  • (int)a()
  • &class->data
  • obj->*fun()

请记住一个重要方法:当我们需要改变运算符的结合顺序(C++默认的优先级不是我们的意愿)时,可以通过添加()来人为强制指定优先顺序,因为()是除了::以外具有最高优先级的一类运算符。

简单例子:以[]和*为例探讨运算符结合规律

下面的p1, p2是数组,p3是指针:

  1. int *p1[2]; // p1是一个数组,元素个数为2,每个元素为(int*)
  2. int *(p2[2]); // 等价于*p2[2],p2是一个数组
  3. int (*p3)[2]; // p3是一个指针,指向一个int数组,这个int数组的元素个数必须是2!

因此只要记住两点即可:

  • **[]的优先级高于* **:即*p1[2]*(p1[2])等价。
  • 这个优先级同时适用于定义语句(*为指针定义符)执行语句(*为解引用符)中:
  1. int *p1[2]; // 定义语句:先看[]:p1是一个数组,元素个数为2,每个元素为(int*)。等价于*(p1[2])
  2. int (*p2)[2]; // 定义语句:先看*: p2是一个指针,指向一个int数组,这个int数组的元素个数必须是2!
  3. cout << "*p1[0] = " << *p1[0] << endl; // 执行语句:先看[]:先取第0个元素,再解引用。等价于*(p1[0])
  4. cout << "(*p2)[0] = " << (*p2)[0] << endl; // 执行语句:先看*:先解引用,再取第0个元素

完整示例:

  1. #include <iostream>
  2. using namespace std;
  3. int main(){
  4. // []的优先级高于*,因此下面的p1是数组,p2是指针:
  5. int *p1[2]; // p1是一个数组,元素个数为2,每个元素为(int*)。等价于*(p1[2])
  6. int (*p2)[2]; // p2是一个指针,指向一个int数组,这个int数组的元素个数必须是2!
  7. int a = 1, b = 2;
  8. int c[2] = {4,5};
  9. p1[0] = &a;
  10. p1[1] = &b;
  11. p2 = &c;
  12. cout << "*p1[0] = " << *p1[0] << endl;
  13. cout << "*p1[1] = " << *p1[1] << endl;
  14. cout << "*(p1[0]) = " << *(p1[0]) << endl; // 与上面两条等价
  15. cout << "*(p1[1]) = " << *(p1[1]) << endl;
  16. cout << "(*p2)[0] = " << (*p2)[0] << endl;
  17. cout << "(*p2)[1] = " << (*p2)[1] << endl;
  18. return 0;
  19. }

输出:

  1. *p1[0] = 1
  2. *p1[1] = 2
  3. *(p1[0]) = 1
  4. *(p1[1]) = 2
  5. (*p2)[0] = 4
  6. (*p2)[1] = 5

复杂例子:探讨当左结合和右结合运算符同时存在时如何界定优先级

下面的例子比较复杂,需要耐心仔细阅读和体会。如果这个例子能搞清楚,那么相信你对运算符优先级的理解将会上升一个档次。

这个例子研究了当左结合和右结合运算符同时存在时的结合顺序,同时也演示了可以使用()强制人为指定结合顺序:

  1. #include <iostream>
  2. #include <string>
  3. using namespace std;
  4. class Student{
  5. public:
  6. Student(string name, int id):_name(name),_id(id){}
  7. void printInfo(){
  8. cout << "I am a student. My name is " << _name << ". My id is " << _id << endl;
  9. }
  10. void operator()(){
  11. printInfo();
  12. }
  13. protected:
  14. string _name;
  15. int _id;
  16. };
  17. class Student2 : public Student{
  18. public:
  19. Student2(string name, int id):Student(name, id){}
  20. void printInfo(){
  21. cout << "I am a super Student!!! " << endl;
  22. }
  23. void operator()(){
  24. cout << "I am Student2!!!" << endl;
  25. }
  26. };
  27. struct StudentWrapper{
  28. Student* _ps;
  29. StudentWrapper(Student* ps):_ps(ps){}
  30. Student* operator()(){return _ps;}
  31. };
  32. int main(){
  33. // .和(), ->和()平级:从左向右
  34. cout << "-----------------1------------------" << endl;
  35. Student s1("Bob",101), s2("Jack", 102);
  36. Student *ps1 = new Student("Eric",103);
  37. s1.printInfo();
  38. s2.printInfo();
  39. ps1->printInfo();
  40. // .高于*:先结合.
  41. cout << "-----------------2------------------" << endl;
  42. // 下面这条语句报错:先调用.printInfo(),再*,因此会报错
  43. // *ps1.printInfo(); // error: request for member 'printInfo' in 'ps1'
  44. (*ps1).printInfo();
  45. // .和()高于*:先结合()和.(从右向左),最后结合*
  46. cout << "-----------------3------------------" << endl;
  47. StudentWrapper sw(ps1);
  48. // 下面这条语句报错:先结合sw(),再结合.printInfo(),最后结合*,因此会报错
  49. // *sw().printInfo(); // error: request for member 'printInfo' in 'sw.StudentWrapper::operator()()'
  50. (*sw()).printInfo(); // correct:先sw(),再*sw(),再(*sw()).printInfo()
  51. // 下面这条语句报错:先结合sw(),再结合(),最后结合*,因此会报错
  52. // *sw()(); // error: expression cannot be used as a function
  53. (*sw())(); // correct:先sw(),再*sw(),再(*sw())()
  54. // (int)和()/[]:先结合()和[],再强转
  55. cout << "-----------------4------------------" << endl;
  56. Student2 ss("Alice", 999), sss("Jason", 998), ssArray[2] = {ss, sss};
  57. ss(); // 调用Student2::operator()
  58. // 下面这条语句报错,因为会先结合ss(),再强制转换
  59. // (Student)ss(); // error: invalid use of void expression
  60. ((Student)ss)(); // correct: 调用Student::operator()
  61. // 下面这条语句报错,因为会先结合ssArray[0], 再ssArray[0](),再强制转换
  62. // (Student)ssArray[0](); // error: invalid use of void expression
  63. ((Student)ssArray[1])(); // correct:将ssArray[1]强制转换为Student类型后,调用其()方法
  64. // ()高于.*和->*:先结合()
  65. cout << "-----------------5------------------" << endl;
  66. void (Student::*fp)();
  67. fp = Student::printInfo;
  68. // s1.*fp(); // error: must use '.*' or '->*' to call pointer-to-member function in 'fp (...)'
  69. (s1.*fp)();
  70. (s2.*fp)();
  71. // ps1->*fp(); // error: must use '.*' or '->*' to call pointer-to-member function in 'fp (...)'
  72. (ps1->*fp)();
  73. // (int)高于.*和->*:先结合(int)
  74. cout << "-----------------6------------------" << endl;
  75. Student2 *ssp = &sss; // Jason
  76. void (Student2::*fp2)();
  77. fp2 = Student2::printInfo;
  78. (ss.*fp2)();
  79. ((Student)ss.*fp)(); // 先将ss强转为Student,然后调用Student::printInfo(),注意是.*fp而不是.*fp2
  80. ((Student*)ssp->*fp)(); // 先将ssp强转为Student*,然后调用Student::printInfo(),注意是.*fp而不是.*fp2
  81. // *高于.*和->*:先结合*
  82. cout << "-----------------7------------------" << endl;
  83. (*ssp.*fp2)(); // 先*ssp,再.*fp2
  84. Student2 **sspp = &ssp;
  85. (*sspp->*fp2)(); // 先*sspp,再->fp2
  86. delete ps1;
  87. return 0;
  88. }

输出:

  1. -----------------1------------------
  2. I am a student. My name is Bob. My id is 101
  3. I am a student. My name is Jack. My id is 102
  4. I am a student. My name is Eric. My id is 103
  5. -----------------2------------------
  6. I am a student. My name is Eric. My id is 103
  7. -----------------3------------------
  8. I am a student. My name is Eric. My id is 103
  9. I am a student. My name is Eric. My id is 103
  10. -----------------4------------------
  11. I am Student2!!!
  12. I am a student. My name is Alice. My id is 999
  13. I am a student. My name is Jason. My id is 998
  14. -----------------5------------------
  15. I am a student. My name is Bob. My id is 101
  16. I am a student. My name is Jack. My id is 102
  17. I am a student. My name is Eric. My id is 103
  18. -----------------6------------------
  19. I am a super Student!!!
  20. I am a student. My name is Alice. My id is 999
  21. I am a student. My name is Jason. My id is 998
  22. -----------------7------------------
  23. I am a super Student!!!
  24. I am a super Student!!!

C++中指针和数组相关的运算符优先级的更多相关文章

  1. C语言中指针和数组

    C语言数组与指针的那些事儿 在C语言中,要说到哪一部分最难搞,首当其冲就是指针,指针永远是个让人又爱又恨的东西,用好了可以事半功倍,用不好,就会有改不完的bug和通不完的宵.但是程序员一般都有一种迷之 ...

  2. 指针、数组与sizeof运算符

    指针.数组与sizcof运算符 (1)sizeof是c语言的一个运算符(主要sizeof不是函数,虽然用法很像函数),sizeof的作用是用来返同()里面的变量或者数据类型占用的内存字节数. (2)s ...

  3. C语言中指针和数组的区别

    看<C专家编程>一书,看到数组与指针并不相同一章,遂做了一段测试: 代码: #include <stdio.h> #include <stdlib.h> int m ...

  4. C语言中 指针和数组

    C语言的数组表示一段连续的内存空间,用来存储多个特定类型的对象.与之相反,指针用来存储单个内存地址.数组和指针不是同一种结构因此不可以互相转换.而数组变量指向了数组的第一个元素的内存地址. 一个数组变 ...

  5. 上机实践 - - 一个例子了解C/C++中指针与数组的区别

    本例子来自于<剑指Offer>(P37) 解答如下: size1:20 data1是一个数组,sizeof(data1)是求数组大小. 这个数组包含5个整数,每个整数4个字节,共20字节. ...

  6. 关于C++中的指针、数组

    C++中指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式:将整数变量加一后,其值将增加1:将指针变量加一后,增加的量等于其指向的数据类型的字节数: 指针中存储的是地址,地址在形式上和整数 ...

  7. 娓娓道来c指针 (3)指针和数组

    (3)指针和数组 在c中指针和数组似乎有着千丝万缕的关系.事实上它们不是一回事:指针是指针,数组是数组.两者不同样. 说它们有关系,只是是由于常见这种代码: int main() { int arra ...

  8. 听翁恺老师mooc笔记(5)--指针与数组

    如果我们通过函数的参数将一个数组传递到参数中去,那么在函数里接收到的是什么东西呢?我们知道如果传递一个普通变量,那么参数接收到的是值,如果传递一个指针变量,参数接收到的也是值,只不过这时的值是地址.那 ...

  9. C++指针和数组的区别(不能混用的情况)

    通常情况下,C++中指针和数组是可以混用的,但是,在编写字符数组的全排列的时候,混用却出了问题,因此,今天特地mark一下,以备日后查找 这里整理的,不包括用new开辟的动态数组 1.数组一旦声明,我 ...

  10. C++指针与数组

    对数组地址的理解,如 int c[2] = {2,3}; int(*cp)[2] = &c; cout << &c[0] << c << cp &l ...

随机推荐

  1. docker 容器卷

    创建各种卷 [root@docker ~]# docker volume create mqy-vo101 mqy-vo101 [root@docker ~]# docker inspect mqy- ...

  2. Jenkins 配置即代码(Configuration as Code)详解

    1.概述 在<Centos7下安装配置最新版本Jenkins(2.452.3)>这篇博文中讲解了如何安装Jenkins,虽然在安装Jenkins时安装了一些必备的推荐插件,但在企业环境中使 ...

  3. 【Java】MultiThread 多线程 Re01

    学习参考: https://www.bilibili.com/video/BV1ut411T7Yg 一.线程创建的四种方式: 1.集成线程类 /** * 使用匿名内部类实现子线程类,重写Run方法 * ...

  4. 【SpringBoot】08 探索配置方式 Part4 优先加载的路径

    配置文件的加载位置: SpringBoot启动会扫描i以下为位置的applicationproperties 或者application.yml文件,作为springboot的默认配置文件 优先级从高 ...

  5. 从.net开发做到云原生运维(五)——云原生时代绕不开的Kubernetes

    1. 前言 前面的几篇文章主要是讲.net技术栈里的web开发技术,只是单纯的开发,从一个简单的项目到最后的打包成镜像进行分发. Kubernetes算是开启了一个新时代. 2. Kubernetes ...

  6. 第7期(大连站)—— OpenHarmony城市技术论坛:边缘智能

    PS. 为了进一步的推动国产信息化,国内的各个高校也是踊跃参与呢.

  7. 《最新出炉》系列小成篇-Python+Playwright自动化测试-67 - 模拟手机浏览器兼容性测试

    1.简介 在日常工作中,我们会遇到需要使用不同的硬件设备测试兼容性的问题,尤其是现在手机型号基本上是每个厂家每年发布一款新机型,而且手机的屏幕大小分辨率五花八门的,我们基本不可能全部机型都用真机测试一 ...

  8. worktree的路径的文件夹自己重命名后发现没有git

    1. worktree title: worktree的路径的文件夹自己重命名(修改名称)后发现没有git了 keyword: git worktree repair prune 快速方法 问题:父级 ...

  9. Linux驱动| Linux内核 RTC时间架构

    上一篇文章我们给大家讲解了基于瑞芯微rk3568平台芯片hym8563驱动的移植,本文给大家详细讲解Linux内核的时间子系统. <Linux驱动|rtc-hym8563移植笔记> 一.L ...

  10. 超越Perplexity的AI搜索引擎框架MindSearch

    超越Perplexity的AI搜索引擎框架MindSearch 介绍 MindSearch 是InternLM团队的一个开源的 AI 搜索引擎框架,由中科大和上海人工智能实验室联合打造的,具有与 Pe ...