您的位置:首页 > 其它

const, static, inline, #define的用法以及关系

2016-06-04 11:47 176 查看
在我们写代码的过程中,添加合适恰当的相应修饰符去告诉编译器该怎么做,限制编译器在我们背后偷偷的做一些出乎我们意料的行为,这样方能提高程序的鲁棒性。作为程序员,我们是主宰,应该由我们来明确告诉编译器,我们希望它做什么,不希望它做什么,而不是等发生错误的时候才花大量的时间去debug。合理应用各种修饰符,就像我们为程序开凿了一条河床,让它按照我们的意愿流淌,何乐而不为?

语言有很多修饰符,我们在这里只分析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的用法:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  static inline #define const