const、define、static、extern

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了const、define、static、extern相关的知识,希望对你有一定的参考价值。

参考技术A extern用于变量的声明,告诉编译器:已经存在一个全局变量,但是不在当前的编译单元内,需要连接的时候在其他编译单元中寻找。

修改变量作用域为当前编译单元,变量生命周期不变;
避免重复定义全局变量
2.2. 修饰局部变量 -
修改变量的生命周期为整个工程周期,变量作用域不变

const修饰右边的变量,用来限制变量为只读属性。

在程序的预编译阶段进行替换处理。

区别:
1.define宏是在预处理阶段展开。
const常量是编译运行阶段使用。
2.define不做检查,不会报编译错误,只是替换。const会编译检查,会报编译错误
3.define在展开的时候才分配内存,展开几次分配几次内存。const在定义的时候会分配一次内存到静态区,使用时不重复分配
4.define可以定义一些简单的运算函数

声明一个只读的静态变量

在多个文件中经常使用的同一个全局变量。
使用场景:
1、.h文件中声明

2、.m文件中赋值

3、pch文件中导入头文件即可在整个项目中访问

这里直接访问即可,都不用放到.pch文件中,因为默认权限是internal
public : 最大权限,可以在当前framework和其他framwork中访问;
internal : 默认权限,可以在当前framework中随意访问;
private : 私有权限,只能在当前文件中访问;

一般常量的话,都用extern const 来代替define。
因为一旦定义#define的方式,整个工程将被重新编译,这样带来的时间浪费可想而知
当然了很多情况还是代替不了的,一般定义常量的时候是应该使用这种方式来定义,不过也只是常量宏不被推荐,但是类函数宏用的还是很方便的,
const、#define的优缺点
编译器可以对const进行类型安全检查。而对#define只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

这种情况还可能出现以下错误
Sending 'const NSString *__strong' to parameter of type 'NSString *' discards qualifiers
原因是需要 NSString* 的地方使用了 const NSString*

.h中 extern const NSString* 替换为 extern NSString* const
.m中 const NSString* 替换为 NSString* const
解释:前者相当于指针本身不可修改,后者表示指针指向的内容不可修改,两者的作用都是使字符串只可读不可写。

c艹进阶编程

前排提示:此文并不适合初学者阅读

目录

替换#define

用const替换

用enum替换

用inline替换

constexpr及const的尽可能使用

不同文件的static参数初始化顺序


替换#define

用const替换

如果各位对编译原理熟悉,应该知道什么是符号表,如果不懂,可以去翻翻。

我们都知道#define在预编译阶段就会被处理(通常情况下),因此在程序编译过程中,如果出现错误,我们看到的报错往往是#define后面的内容,本身符号不会显示在错误提示栏中,这样很容易造成困惑。因此我们经常用const替换define;有两个特殊情况:

  1. 指针-》常量指针常量或者直接string
    const char* const p="I love wqm"
    
    or
    
    const std::string p("I love wqm")
    
  2. 类成员变量-》常量静态成员变量:初始化可以在声明式中赋予,定义式可以不带任何东西,旧式编译器不支持这种操作,只能在定义式中赋值,
    这样就会导致一个问题:如果我们希望在nums数组定义的时候使用这个常量,我们在外面定义,编译器是无法通过的,因为它想坚持知道num的值。
    class test
    {
        static const num=5;
        int nums[num]
    }
    const int test::num;

用enum替换

在旧式编译器中,如果不支持内部定义值,我们可以用enum代替,它与const有两点区别

1.其实enum更像define,因为不可以取地址,如果不希望有指针指向这个对象,推荐用enum,
2.不会多分配空间,不够优秀的编译器可能会给const分配额外的空间

class test
{
    enum{num=5};
    int nums[num]
}

用inline替换

宏定义的一个很大的有点就是可以定义简单的函数,但是不会招致函数调用带来的额外开销,比如额外的堆栈调用,比如这个:

#define MY_MAX(a,b) f((a)>(b)?(a):(b))

但是其实这种写法很傻,因为符号优先级我们必须对所有实参加上小括号,否则运行的时候可能会与宏左右的运算符提前运算导致达不到理想的结果。再比如说这种调用方式:

int a=5,b=0;
MY_MAX(++a,b);
My_MAX(++a,b+10);

如果传入的本身就是一个带操作符的数,很可能结果会与我们想法事与愿违。

我们可以用template inline函数替换简单的函数

template<typename T>
inline void myMax(const T& a,const T& b)
{
    f(a>b?a:b);
}

这种方式就不用担心多次求值,另一个优点是可以在类内实现,是有作用域的,而define无法定义作用域。

constexpr及const的尽可能使用

c++14版本进一步扩充了constexpr的使用,要求为传入和输出都是const,这两者是有区别的。const可以表示字面值常量也可以表示只读,而constexpr只能表示字面值常量,如果用constexpr作为函数前缀,编译器就能大胆地优化。

需要额外强调的是,常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果,但是常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。

但是值得注意的是,并不是所有的函数加上都能被当作字面值常量,如果传入的参数并不是字面值常量而是只是代表只读,那么还是会被当做普通函数处理。此时constexpr只是推荐编译器优化,但是并不一定会优化,比如下面这个,还是运行时编译的。

using namespace std;
constexpr int f(const int x)
{
    return x+10;
}
int main()
{
    int i=cin.get();
    cout<<f(i)<<endl;
}

const在普通函数上一个作用是在返回值是const的时候表示只读,因此如果有比如类似赋值之类的操作是无意义的,可以避免bug的出现。即使返回的是值,也是一个副本而不是值,这类返回会被编译器报错,比如这样:

class num 
{
public:
    int num = 0;
    int get() 
    {
        return this->num;
    }
};
int main()
{
    num n1;
    n1.num = 1;
    n1.get() = 2;
    system("pause");
}

如果类内部对一个函数的返回值做const重载,会有不同的效果:

using namespace std;
class nums 
{
public:
    int num = 0;
    nums(int number) :num(number) {};
    int get() 
    {
        cout << "not const" << endl;
        return this->num;
    }
    const int get() const
    {
        cout << "const" << endl;
        return this->num;
    }
};
int main()
{
    nums n1(1);
    const nums n2(2);
    n1.get();
    n2.get();
    system("pause");
}

输出结果如下,因此我们可以根据是否是const制定不同的函数。

 如果我们不写const版本,有const版本的编译无法通过

如果俩内容一样,那么呢简单的解决方法是这样,反之并不推荐

using namespace std;
class nums 
{
public:
    int num = 0;
    nums(int number) :num(number) {};
    int get() 
    {
        auto&& temp = static_cast<const nums>(*this).get();
        return const_cast<int&>(temp);
    }
    const int get() const
    {
        cout << "const" << endl;
        return this->num;
    }
};
int main()
{
    nums n1(1);
    const nums n2(2);
    n1.get();
    n2.get();
    system("pause");
}

还有值得指出的是如果希望针对一个const成员函数希望改变部分成员对象,定义的时候可以加上mutable消除掉对这个变量的束缚,比如这样。

using namespace std;
class nums 
{
public:
    int num = 0;
    mutable int num2 = 0;
    nums(int number) :num(number) {};
    int get() 
    {
        auto&& temp = static_cast<const nums>(*this).get();
        return const_cast<int&>(temp);
    }
    const int get() const
    {
        cout << "const" << endl;
        num2 += 1;
        return this->num;
    }
};
int main()
{
    nums n1(1);
    const nums n2(2);
    n1.get();
    n2.get();
    system("pause");
}

不同文件的static参数初始化顺序

首先先说明这个顺序是无解的,比如我们在文件a里面定义了一个classA,然后文件b里面定义了另一个classB,我们的类B需要类A的一个static参数来初始化,这种参数叫做no_local static param,而编译器本身没办法保证这个东西是初始化在我们定义类之前的,因此我们需要用一个额函数做一层封装:

A& getA()
{
    static A a;
    return a;
}

如果涉及多线程,我们需要考虑重排问题,就需要加fence防止汇编重排,从而生成多个对象。有点像设计模式种的单例模式了。具体解决方案可以看我设计模式部分的博客。

以上是关于const、define、static、extern的主要内容,如果未能解决你的问题,请参考以下文章

PHP中const,static,public,private,protected的区别

php中const和define的区别

OC中extern、static、const和宏定义

const与static的区别

php中const和static的区别和联系

1.尽量以const ,enum,inline替换define