const, static, inline, #define的用法以及关系
2016-06-04 11:47
176 查看
在我们写代码的过程中,添加合适恰当的相应修饰符去告诉编译器该怎么做,限制编译器在我们背后偷偷的做一些出乎我们意料的行为,这样方能提高程序的鲁棒性。作为程序员,我们是主宰,应该由我们来明确告诉编译器,我们希望它做什么,不希望它做什么,而不是等发生错误的时候才花大量的时间去debug。合理应用各种修饰符,就像我们为程序开凿了一条河床,让它按照我们的意愿流淌,何乐而不为?
语言有很多修饰符,我们在这里只分析const, static,enum, inline,#define的用法以及他们的关系。
const的用法可以概括为下面几个方向:
1. 修饰指针,迭代器。
2. const 修饰local 变量
3. const 修饰函数参数以及返回值
这应该是用得最多的用法。修饰函数参数,可以告诉编译器该参数在函数作用域内不会改变, 请您放心做优化, 编译器还可以根据这个发现一些书写错误, 如下:
只要我们认为这些参数在函数内不会发生改变, 特别是指针, 迭代器,引用或者数组, 我们应该斟酌着加上合适的const修饰符, 可以避免上述类似错误,因为编译器会发出错误警告。 否则我们只能进入彻夜的调试大会。
const修饰函数的返回值, 我们的初衷是不希望函数的返回值被赋值, 返回值为只读, 不可写。
4. const 修饰类成员函数
在C++中,const 修饰类成员函数非常有用,通过const修饰成员函数,一方面提高函数接口可能性,读者阅读代码可以很清楚的知道在这个函数内部不会改变成员变量,易于明白作者的意图;另一方面可以操作相关的const对象;还有一点,我们经常会忽略的是,两个成员函数如果只是常量性不同,可以被重载。
上述代码有两个需要注意的地方, 一是const修饰的成员函数返回值应该采用const, const修饰的成员函数我们要做到不仅在函数内部成员变量不被改变,与此同时也不能在返回值留下被修改的漏洞。
二是const属性不同的成员函数重载,如下:
static的用法:
static在c语言中的用法有两方面:
1. 修饰函数以及全局变量, 使被修饰的对象的可见性仅限于被定义的文件中
上述static的用法在C代码中比较常见,但是在C++代码中,我们可以用更加有效的方法替代, 那就是无名namesapce, 既然namespace没有名字, 你在其他文件当然是无法访问的,也就是说无名namespace的作用域只能在定义文件内。代码如下:
2.修饰local变量
static修饰local变量,使得local变量的生命周期上升到跟全局变量一样,但是不会改变local变量的作用域。 static修饰的local变量会被存储在全局变量/静态变量区内, 而不是在栈中。
3. 修饰类成员变量以及成员函数
在c++中,static新增的用法是修饰成员变量以及成员函数。 static修饰的成员变量是隶属于整个类别所有成员所共享, 不是仅仅属于某一个类对象。static修饰的成员函数不能操作非static成员变量。这是因为普通成员函数会有个默认的形参为this指针, this指针所指向对象为具体的某个类对象, static修饰的成员函数不存在this指针。
inline的用法
引用《effective c++》的一句话: inline函数看起来像函数,动作像函数,却不是函数,比#define表现要好,调用inline函数不会蒙受调用普通函数的额外开销。
下面我们分别对上面的话进行解析。
inline函数在书写的时候完全是按照函数的格式, 所以看起来像函数; 动作像函数,主要是指调用inline函数时,会像普通函数一样进行形参检查,而宏不会对参数进行检查,所以有时候一不小心就会出错(即使你的本意不是这样)。
inline函数的本意是在每一个调用inline函数的地方都会以inline函数的本体替换之,所以不会带来额外的普通函数调用开销。普通函数的调用开销一般有保存返回地址,保存实参,保存某些寄存器值,而被调用函数不一定跟调用函数在同一个内存页,因此可能还会涉及到内存页面置换带来的额外开销。
上述所说的inline函数那么多优点,是不是我们可以无所顾忌的使用inline函数呢?不是的。过度热衷inline 函数会导致代码膨胀,进而造成更多的内存换页行为,还会降低cache的命中率,直接造成运行效率降低。反之,如果函数的本体很小,那么inline直接替换以后的函数本体可能比未替换的更小。运行效率不仅没有下降,反而有提高。
一般来说,函数本体只有几行,且没有循环递归等较为复杂逻辑运算的均可以定义为inline函数。此外,并不是我们用inline去声明一个函数或者将一个函数定义在class内,它就一定为inline函数。请记住,inline只是我们对编译器的一个请求,最终是不是得看编译器的决定。
inline函数一般被定义于头文件。因为大部分的编译器都是在编译的时候对inline函数进行函数本体替换,这个时候编译器需要在每一个调用inline函数的地方都看到inline的函数本体。
virtual函数被定义为inline函数的愿望一般不能实现。因为inline函数在编译的时候就要进行函数替换,需要知道调用的具体函数是哪一个,此过程发生在链接与运行之前。而virtual函数意味着,不知道调用的是哪一个函数,直到函数运行到此。
在工程实现的初期,为了便于测试代码debug,我们先不要定义任何inline函数,否则无法设定断点之类的debug手段。在测试成功,进行优化时,我们可以再考虑将哪些函数进行inline。还有一点需要注意的是,因为inline函数是在每一个调用处以本体替换,因此只要inline函数本体发生变化,每一个调用它的文件都要重新编译链接,可能会消耗更多的时间。如果是非inline函数只要一个文件重新编译,其它文件链接即可。这是在inline函数时要考虑的。
define的用法:
语言有很多修饰符,我们在这里只分析const, static,enum, inline,#define的用法以及他们的关系。
const的用法可以概括为下面几个方向:
1. 修饰指针,迭代器。
char str[] = "Hello World"; const char *pStr = str; //常量指针,指针指向的对象是一个常量,无法通过指针修改对象的值, //但是可以改变指针的指向 *pStr = 'h'; //error pStr++; //right char* const pStr = str; //指针常量, 常量的内容为一个指针,我们无法改变指针本身的值, //但是可以改变指针所指向对象的值 *pStr = 'h'; //right pStr++; //error const char* const pStr = str; //指针本身已经指针所指向的值均不能改变, //可以理解为完全的常量 //迭代器 vector<int>::const_iterator IteVecInt; //常量迭代器, 同义与常量指针 const vector<int>::iterator IteVecInt; //迭代器常量, 同义与指针常量
2. const 修饰local 变量
const int allocNum = 1000; int array[allocNum];
3. const 修饰函数参数以及返回值
这应该是用得最多的用法。修饰函数参数,可以告诉编译器该参数在函数作用域内不会改变, 请您放心做优化, 编译器还可以根据这个发现一些书写错误, 如下:
void functionA(const int &a) { int b; b = 10; if(a = b) { //应该为 a == b, 书写错误 ... } ... }
只要我们认为这些参数在函数内不会发生改变, 特别是指针, 迭代器,引用或者数组, 我们应该斟酌着加上合适的const修饰符, 可以避免上述类似错误,因为编译器会发出错误警告。 否则我们只能进入彻夜的调试大会。
const修饰函数的返回值, 我们的初衷是不希望函数的返回值被赋值, 返回值为只读, 不可写。
const int& functionB(const int &a) { int b; b = 10; if(a = b) { //应该为 a == b, 书写错误 ... } ... return a; } int num = 10; int numBack = functionB(num); //right functionB(num) = 5; //error;
4. const 修饰类成员函数
在C++中,const 修饰类成员函数非常有用,通过const修饰成员函数,一方面提高函数接口可能性,读者阅读代码可以很清楚的知道在这个函数内部不会改变成员变量,易于明白作者的意图;另一方面可以操作相关的const对象;还有一点,我们经常会忽略的是,两个成员函数如果只是常量性不同,可以被重载。
class book { public: .... const char& operator[](std::size_t position) const { return bookText[position];} char& operator[](std::size_t position) { return bookText[position]; } ... private: std::string bookText; }
上述代码有两个需要注意的地方, 一是const修饰的成员函数返回值应该采用const, const修饰的成员函数我们要做到不仅在函数内部成员变量不被改变,与此同时也不能在返回值留下被修改的漏洞。
二是const属性不同的成员函数重载,如下:
book bookA("C++ primer"); std::cout<<bookA[0]; //调用char& operator[](std::size_t position) bookA[0] = 'B'; // "B++ primer" is right book bookB("C++ primer"); std::cout<<bookB[0]; //调用const char& operator[] (std::size_t position) const bookA[0] = 'B'; // error, 返回值为const,不能修改
static的用法:
static在c语言中的用法有两方面:
1. 修饰函数以及全局变量, 使被修饰的对象的可见性仅限于被定义的文件中
// calNum.cpp static int num = 10; static int calNum(int ); //可见性均只限于calNum.cpp, 在其他文件中不可见
上述static的用法在C代码中比较常见,但是在C++代码中,我们可以用更加有效的方法替代, 那就是无名namesapce, 既然namespace没有名字, 你在其他文件当然是无法访问的,也就是说无名namespace的作用域只能在定义文件内。代码如下:
namespace { int number = 10; int calNum(int num) { return num+1;} }; int main() { number = 11; int newNum = calNum(number); return 0; }
2.修饰local变量
static修饰local变量,使得local变量的生命周期上升到跟全局变量一样,但是不会改变local变量的作用域。 static修饰的local变量会被存储在全局变量/静态变量区内, 而不是在栈中。
int calTimes(void ) { static int times = 0; return ++times; } int num1 = calTImes(); // 1 int num2 = calTimes(); // 2
3. 修饰类成员变量以及成员函数
在c++中,static新增的用法是修饰成员变量以及成员函数。 static修饰的成员变量是隶属于整个类别所有成员所共享, 不是仅仅属于某一个类对象。static修饰的成员函数不能操作非static成员变量。这是因为普通成员函数会有个默认的形参为this指针, this指针所指向对象为具体的某个类对象, static修饰的成员函数不存在this指针。
class textBook { public: static int getNum() {return num;} //right static int getNum1() {return num1;} //Error private: int num1; static int num; };
inline的用法
引用《effective c++》的一句话: inline函数看起来像函数,动作像函数,却不是函数,比#define表现要好,调用inline函数不会蒙受调用普通函数的额外开销。
下面我们分别对上面的话进行解析。
inline函数在书写的时候完全是按照函数的格式, 所以看起来像函数; 动作像函数,主要是指调用inline函数时,会像普通函数一样进行形参检查,而宏不会对参数进行检查,所以有时候一不小心就会出错(即使你的本意不是这样)。
inline函数的本意是在每一个调用inline函数的地方都会以inline函数的本体替换之,所以不会带来额外的普通函数调用开销。普通函数的调用开销一般有保存返回地址,保存实参,保存某些寄存器值,而被调用函数不一定跟调用函数在同一个内存页,因此可能还会涉及到内存页面置换带来的额外开销。
上述所说的inline函数那么多优点,是不是我们可以无所顾忌的使用inline函数呢?不是的。过度热衷inline 函数会导致代码膨胀,进而造成更多的内存换页行为,还会降低cache的命中率,直接造成运行效率降低。反之,如果函数的本体很小,那么inline直接替换以后的函数本体可能比未替换的更小。运行效率不仅没有下降,反而有提高。
一般来说,函数本体只有几行,且没有循环递归等较为复杂逻辑运算的均可以定义为inline函数。此外,并不是我们用inline去声明一个函数或者将一个函数定义在class内,它就一定为inline函数。请记住,inline只是我们对编译器的一个请求,最终是不是得看编译器的决定。
inline函数一般被定义于头文件。因为大部分的编译器都是在编译的时候对inline函数进行函数本体替换,这个时候编译器需要在每一个调用inline函数的地方都看到inline的函数本体。
virtual函数被定义为inline函数的愿望一般不能实现。因为inline函数在编译的时候就要进行函数替换,需要知道调用的具体函数是哪一个,此过程发生在链接与运行之前。而virtual函数意味着,不知道调用的是哪一个函数,直到函数运行到此。
在工程实现的初期,为了便于测试代码debug,我们先不要定义任何inline函数,否则无法设定断点之类的debug手段。在测试成功,进行优化时,我们可以再考虑将哪些函数进行inline。还有一点需要注意的是,因为inline函数是在每一个调用处以本体替换,因此只要inline函数本体发生变化,每一个调用它的文件都要重新编译链接,可能会消耗更多的时间。如果是非inline函数只要一个文件重新编译,其它文件链接即可。这是在inline函数时要考虑的。
define的用法:
相关文章推荐
- 理解C#编程中的静态类和静态成员以及密封类
- C++中const的用法详细总结
- C++中const用法小结
- c++中inline的用法分析
- C语言中static的作用及C语言中使用静态函数有何好处
- 从汇编看c++中函数里面的static关键字的使用说明
- C++中的const和constexpr详解
- 详解C++中的const关键字及与C语言中const的区别
- C++的static关键字及变量存储位置总结
- C++ 中const和复合类型
- C++实现inline hook的原理及应用实例
- PHP中new static() 和 new self() 的区别介绍
- C#静态static的用法实例分析
- static关键字的作用详解
- c++中const的使用详解
- 浅谈C/C++中的static与extern关键字的使用详解
- C语言基础知识点解析(extern,static,typedef,const)
- c++ 尽量不要使用#define 而是用const、enum、inline替换。
- python django - static文件处理与线上部署测试
- Java static 关键字