指针左右原则

本文详细解析了C语言中复杂指针声明的解读方法,通过右左法则,从最内层的未定义标识符开始,逐步向外解析括号内的内容,最终完整理解声明的含义。实例演示了如何解析不同层级的指针声明,包括函数指针、数组指针等,以及如何识别合法与非法的指针声明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的英文原文是这样说的:

The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed. 


这段英文的翻译如下:

右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。

  笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。

  现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:

int (*func)(int *p);

首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是int。

int (*func)(int *p, int (*f)(int*));

func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。

int (*func[5])(int *p);

func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。


int (*(*func)[5])(int *p);

func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

int (*(*func)(int *p))[5];

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。

要注意有些复杂指针声明是非法的,例如:

int func(void) [5];

func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。

int func[5](void);

func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。

作为练习,下面列几个复杂指针声明给读者自己来解析,答案放在第十章里。

int (*(*func)[5][6])[7][8];

int (*(*(*func)(int *))[5])(int *);

int (*(*func[7][8][9])(int*))[5];

  实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性,例如对于声明:

int (*(*func)(int *p))[5];

可以这样分解:

typedef int (*PARA)[5];
typedef PARA (*func)(int *);

<think>我们正在讨论C++中const关键字的使用方法及其修饰作用。根据引用内容,const关键字主要用于强制保护,防止被修饰的目标被意外修改。下面将详细解释const修饰变量和函数的作用。 ### 1. const修饰变量 当const用于修饰变量时,该变量变为常量,初始化后不可修改。根据引用的内容,我们可以分为以下几种情况: #### (1) 普通变量 - 基本语法:`const 类型 变量名 = 初始值;` 或 `类型 const 变量名 = 初始值;` - 特点:必须在定义时初始化,之后不能修改。 - 示例: ```cpp const int a = 10; // 正确,a被初始化为10 a = 20; // 错误,a是const变量,不能修改 ``` #### (2) 类中的成员变量(常成员变量) - 根据引用[1]:常成员变量只能通过初始化列表方式初始化,一旦初始化,其值不能被修改。 - 示例: ```cpp class MyClass { public: const int x; // 常成员变量 MyClass(int val) : x(val) {} // 必须通过初始化列表初始化 // 错误:不能在构造函数体内赋值 // MyClass(int val) { x = val; } }; ``` #### (3) 指针和引用 - `const`可以修饰指针本身或指针指向的数据: - `const int* p`:指向常量的指针指针指向的数据不可修改,但指针可以指向其他地址) - `int* const p`:常量指针指针本身不可修改,但指向的数据可以修改) - `const int* const p`:指向常量的常量指针(两者都不可修改) - `const`修饰引用:`const int& r = a;`,通过引用r不能修改a的值。 ### 2. const修饰函数 根据引用[2][3][4],const修饰函数主要有三种形式: #### (1) 修饰返回值 - 语法:`const 返回类型 函数名(参数列表);` - 作用:防止函数返回值被修改(主要针对返回引用或指针的情况)。 - 示例: ```cpp const int& getValue() { static int x = 10; return x; } // 调用:getValue() = 20; // 错误,不能修改const返回值 ``` #### (2) 修饰形参 - 语法:`返回类型 函数名(const 类型 形参);` - 作用:保护实参,防止在函数内部被修改。常用于传递大对象时避免拷贝,同时防止修改。 - 示例: ```cpp void printValue(const int& val) { // val = 100; // 错误,val是const引用,不能修改 std::cout << val; } ``` #### (3) 修饰成员函数(常成员函数) - 语法:`返回类型 函数名(参数列表) const;` - 作用:根据引用[1][3][4],const成员函数不能修改任何成员变量的值(除非成员变量被声明为`mutable`),并且只能调用其他const成员函数。 - 示例: ```cpp class MyClass { int x; public: void setX(int a) { x = a; } int getX() const { // 常成员函数 // setX(10); // 错误,不能调用非const成员函数 return x; } }; ``` ### 3. const修饰对象 根据引用[1]: - `const MyClass obj;` 创建常对象。 - 常对象的所有成员都变为只读,只能调用常成员函数,不能调用非常成员函数。 - 示例: ```cpp const MyClass obj; // obj.setX(10); // 错误,常对象不能调用非常成员函数 int a = obj.getX(); // 正确,getX()是const成员函数 ``` ### 总结 | 修饰目标 | 作用 | 示例 | |----------------|----------------------------------------------------------------------|----------------------------------------------------------------------| | 普通变量 | 变量值不可修改 | `const int a=10;` | | 类成员变量 | 只能通过初始化列表初始化,不可修改 | 在构造函数初始化列表中初始化 | | 指针 | 可修饰指针本身或指向的数据 | `const int* p;` 或 `int* const p;` | | 引用 | 引用变为只读 | `const int& r = a;` | | 函数返回值 | 防止返回值被修改(主要针对返回引用/指针) | `const int& getX();` | | 函数形参 | 保护实参,防止在函数内被修改 | `void func(const int& x);` | | 成员函数 | 不能修改成员变量,只能调用const成员函数 | `int getX() const;` | | 对象 | 对象所有成员只读,只能调用const成员函数 | `const MyClass obj;` | ### 注意事项 1. **const成员函数规则**:根据引用[3],const成员函数内不能调用非const成员函数,即使那个函数没有修改任何成员变量。 2. **mutable成员**:如果类中有`mutable`成员变量,它在const成员函数中可以被修改。 3. **函数重载**:const成员函数和非const成员函数可以形成重载,例如: ```cpp class MyClass { public: int& getX() { return x; } const int& getX() const { return x; } // 重载 }; ``` ### 相关问题 1. 为什么const成员函数不能调用非const成员函数? 2. 如何设计一个类,使其同时支持const和非const对象访问? 3. 在C++中,const_cast在什么场景下使用是安全的? 4. mutable关键字在const成员函数中有什么作用? 5. 如何理解const成员函数返回*this时,返回类型应为const引用? [^1]: 引用自站内内容:const关键字的作用 [^2]: 引用自站内内容:c++ const修饰函数 [^3]: 引用自站内内容:C++:const修饰变量、形参、函数 [^4]: 引用自站内内容:C++ const关键字的总结</think># C++中const关键字详解:修饰变量与函数的作用 ## 一、const修饰变量的作用 ### 1. 基本变量 当`const`修饰基本变量时,**创建不可修改的常量**: ```cpp const int MAX_SIZE = 100; // 声明即初始化 // MAX_SIZE = 200; // 错误:尝试修改常量 ``` - **作用**:编译器保证该值在程序运行期间不变 - **优势**:避免魔法数字,提高代码可读性和安全性[^1] ### 2. 指针变量 `const`在指针中的位置决定其约束对象: ```cpp int value = 10; const int* ptr1 = &value; // 指向常量的指针 int* const ptr2 = &value; // 常量指针 const int* const ptr3 = &value; // 指向常量的常量指针 ``` - `ptr1`:可修改指针地址,**不可修改指向的值** - `ptr2`:**不可修改指针地址**,可修改指向的值 - `ptr3`:指针地址和指向的值都**不可修改**[^1] ### 3. 类成员变量 ```cpp class ImageProcessor { public: ImageProcessor(int w) : WIDTH(w) {} // 必须通过初始化列表初始化 private: const int WIDTH; // 常成员变量 }; ``` - **作用**:创建类实例级别的常量 - **特点**: - 只能通过**初始化列表**初始化[^1] - 每个对象可拥有不同的常成员值 - 生命周期与对象绑定 ### 4. 引用变量 ```cpp int original = 5; const int& ref = original; // ref = 10; // 错误:不能通过const引用修改值 ``` - **作用**:创建只读别名,常用于函数参数传递 - **优势**:避免大对象拷贝开销,同时防止意外修改[^2] ## 二、const修饰函数的作用 ### 1. 修饰函数返回值 ```cpp const std::string& getDefaultName() { static const std::string name = "default"; return name; // 返回const引用防止外部修改 } // getDefaultName() = "new"; // 错误:不能修改const返回值 ``` - **应用场景**:返回类内部状态或静态常量 - **作用**:阻止调用者修改返回值[^2] ### 2. 修饰函数参数 ```cpp void displayImage(const Image& img) { img.render(); // 只能调用const成员函数 // img.modify(); // 错误:不能调用非const方法 } ``` - **作用**:保证函数内部不会修改实参值 - **最佳实践**: - 基本类型:传值(`int`,`float`) - 复合类型:传const引用(`const string&`) - 输出参数:非const指针(`int* result`)[^3] ### 3. 修饰成员函数(核心用法) ```cpp class PixelBuffer { public: uint8_t* getData() const { // const成员函数 return data; } void modifyData() { // 非const成员函数 // 修改操作... } private: uint8_t* data; }; const PixelBuffer buffer; buffer.getData(); // ✅ 允许调用 // buffer.modifyData(); // ❌ 禁止调用 ``` - **语法特征**:`const`关键字位于函数参数列表后 - **核心约束**: 1. **不能修改任何成员变量**(除非`mutable`修饰) 2. **只能调用其他const成员函数**[^3][^4] - **设计哲学**:严格区分"只读操作"和"修改操作"[^4] ## 三、const修饰对象 ```cpp const ImageProcessor processor(1920); // processor.reset(); // 错误:常对象不能调用非常成员函数 ``` - **特点**: - 所有成员变量自动变为只读 - 只能调用const成员函数 - 构造函数/析构函数除外[^1] ## 四、const使用的最佳实践 1. **默认const原则**: ```cpp // 优先选择 const auto& item = container[0]; // 而非 auto& item = container[0]; ``` 2. **接口设计准则**: - 不修改参数的函数应使用`const&` - 不修改对象状态的成员函数应声明为`const` 3. **const与线程安全**: ```cpp class ThreadSafeCounter { public: int getCount() const { std::lock_guard<std::mutex> lock(mtx); // mutable允许 return count; } private: mutable std::mutex mtx; // mutable成员 int count = 0; }; ``` - `mutable`突破const限制,用于**线程同步等特殊场景** ## 五、const的类型系统保护 ```mermaid graph LR A[源代码] --> B[编译器] B --> C{const检查} C -->|违反const约束| D[编译错误] C -->|符合约束| E[生成目标代码] ``` - **编译时检查**:在编译阶段捕获修改常量等错误 - **类型安全增强**:区分`const T*`和`T*`等类型 - **优化可能性**:编译器可利用const信息进行优化[^4] > **关键洞见**:const不是运行时保护机制,而是**开发者与编译器的契约**。它通过类型系统将设计意图转化为可执行的约束规则[^1][^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值