Words Of Life

Top-Level Const And Low-Level Const

  • 摘要:ObjC Const 小坑总结


前言

  • 记得刚学ObjC的时候,总是被顶层const和底层const搞的头晕晕的(也可能是我本人比较笨的原因吧:])。谨以此文,记录我踏过的坑,同时也希望为后人扫个雷

先谈C++ 顶层const与底层const

  • 最早了解到const相关的话题时,是在阅读C++ Primer一书时,里面提到的两个概念

    顶层const:表示指针本身是个常量

    底层const:表示指针所指的对象是一个常量

  • 在C++中,由顶层const与底层const + 指针类型,又衍生了两个概念

    • 常量指针:顶层const指针,即指针不可变,不能再指向其他元素,但指针指向的元素自身可变
    • 指针常量:底层const指针,即指针可变,但指针指向的元素自身不可变
  • 概念本身是很简单的,但是上了代码之后,我们能认清下面哪个是顶层,哪个是底层吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 普通变量
int a = 0;
int b = 1;
int *const ptrA = &a;
// 顶层const,ptrA为常量指针,ptrA不能再指向其他变量
// ==== Error Code ====
// ptrA = &b;
// ==== Allow Code ====
*ptrA = 2;
const int *ptrB = &b;
// 底层const, ptrB为指针常量,ptrB指向的b不能改变
// ==== Error Code ====
// *ptrB = 2;
// ==== Allow Code ====
ptrB = &a;
const int ci = 10;
// 底层const,ci本身不能再改变
// ==== Error Code ====
// ci = 11;
const int *const ptrC = ptrB;
// 左边为底层const,右边为顶层const,ptrC既是常量指针,又是指针常量
// ==== Error Code ====
// ptrC = ptrA;
// ==== Error Code ====
// *ptrC = 2;
const int &rA = a;
int const &rB = b;
// 用于声明引用的都是底层const,这是因为引用本身不能二次赋值
// ==== Error Code ====
// rA = 2;
// rB = 2;
int const *ptrD = ptrB;
// 底层const,ptrD是指针常量,ptrD指向的b本身不能改变
// ==== Error Code ====
// *ptrD = 11;
// ==== Allow Code ====
ptrD = ptrA;
  • 图解

    cpp_const
    • 图解标注
      • 空心箭头表示指向的对象值可变 <=> 实心箭头表示指向的对象值不可变
      • 虚线表示指向对象这种关系可变 <=> 实心箭头表示指向对象这种关系固定了
  • 小总结

    • 指针与const
      • const靠近指针符号
        • => const为顶层const,指针为常量指针,指向关系不能变
      • const两边在中间或靠近数据类型
        • => const为底层const,指针为指针常量,指向关系可变,指向元素不可变
    • 引用与const
      • 引用变量:因为引用不能二次赋值,所以只能声明底层const

再谈ObjC const

  • constObjC中的用途

    • 代替#define声明常量,保证编译器有类型检测(见下图)

      1
      2
      #define kHelloString = @"Hello";
      const NSString *const kGoodbyeString = @"Goodbye";
    • 我们希望能够保证kGoodbyeString不被修改,如果我们不去考虑底层const/顶层const的问题,我们可以直接使用两个const来让他达到和#define一样的效果,但这样其实在使用某些API时,会出现warning,因此我并不建议直接使用两个const

  • 讨论

    • ObjCC++const用法是否一样?很幸运,是的。下述三种const用法均与C++的用法一样

      1
      2
      3
      4
      int a = 1;
      const int *ptrA = &a;
      int const *ptrB = &a;
      int *const ptrC = &a;
    • 那我们要讨论的是什么呢?我们要讨论的是,当指针遇上对象,我们究竟需要什么样的const来达到#define的效果

      • 图解 - 开始

        start
        • 图解标注
          • 在ObjC中,我们把@”xx”称作字符串常量,在ObjC中,实际上有一个字符串常量池,我们可以把它看作一个集合。用到的字符串都会出现在这里,且只出现一次。然后指针指向这块内存,以降低内存消耗。所以,我们代码中的constStringAconstStringBconstStringC实际上都是指向了这块内存。其中,constStringAconstStringB是指针常量,constStringC是常量指针
          • 空心箭头表示指向的对象值可变 <=> 实心箭头表示指向的对象值不可变
          • 虚线表示指向对象这种关系可变 <=> 实心箭头表示指向对象这种关系固定了
      • 接下来,我们来执行一段代码,再来看看状态

        1
        2
        3
        4
        constStringA = @"a";
        constStringB = @"b";
        // ==== Error Code ====
        // constStringC = @"a";
      • 图解 - 执行代码后

        end
        • 图解标注
          • 执行代码后,实际上改变的是指针的指向,而非内存里字符串常量的值
          • 空心箭头表示指向的对象值可变 <=> 实心箭头表示指向的对象值不可变
          • 虚线表示指向对象这种关系可变 <=> 实心箭头表示指向对象这种关系固定了
          • 从图上看,我们可以知道,对于字符串常量对象而言,因为字符串本身就不可变了,我们希望指向他的指针的指向关系不能改变。所以我们需要使用的应该是常量指针(constStringC
    • 上述部分只是针对字符串常量对象而言,针对字符串常量,我们如果想要达到#define的效果,需要使用的应该是常量指针(即顶层const

    • 由于在ObjC中,大部分对象都会涉及到指针,所以对于对象而言,我们首先要使用顶层const。其次我们还要研究对象本身是否可变,如果本身可变,则要用底层const

  • 小总结

    • 对于一般的字符串而言,我们应该使用的是顶层const
    • 如果不想使用两个const,我们则需研究对象本身是否可变,如果本身可变,则要用底层const + 顶层const;否则用一个顶层const即可


  • Last Edited:2016.12.5
  • Author:@Seahub
  • Please contact me if you want to share this Article, 3Q~
五毛也是情, 一元也是爱