C++ 关键字 Const Define Static
作者:AceTan,转载请标明出处!
今天来讨论一下在C++中很常见的三个关键字 Const Define Static.
0x00: const
const 限定符:有时候我们需要定义这样一种变量,它的值是不可改变的。这时候,我们就需要用到const这个关键字了。
const 关键字在各大考试和C++笔试中经常遇到。比如下面这一道面试题:
说出const关键字在下列语句中的作用。
- const int a;
- int const a;
- const int *a;
- int const *a;
- int *const a;
- const int * const a;
- const char* func1();
- int GetX() const;
看完是不是一脸懵逼?看完下面的解读,上面的问题将迎刃而解。
首先需要知道的是const修饰符所修饰的变量值不可改变。
另外需要注意的是,在用const定义关键字时, const int 和 int const 这两种写法是等价的(没有*),它表示所定义的整型变量的值是不可改变的。
现在我们的关键问题是,const修饰符到底是修饰的谁。是这个整型变量,还是指针,还是指针所指向的值。
默认状态下,const 对象仅在文件内有效。 如果想让它在其他文件内也有效,需要添加extern关键字。
引用和const
const 的引用 可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。 与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。
举个栗子:
int v = 123;
const int& r1 = v; // 允许将const int& 绑定到一个普通的int对象上
const int& r2 = 123; // 正确:r2是一个 常量引用
const int& r3 = r1 * 10;// 正确:r3是一个常量引用
int& r4 = r1 * 5; // 错误:r4是一个普通的非常量引用
int& r5 = v; // r5绑定对象v
r5 = 1; // 正确,r5并非一个常量
r1 = 1; // 错误:r1是一个常量引用,值不可更改
在实际编程中,如果写的语法有错误,IDE一般都是会提示的,根据提示修改就好。 但我们应该记住这些常见的特性。
指针和const
指针和const鬼混在一块,那才是噩梦的开始。
指针是对象,而引用不是。因此,可以像其他对象类型一样,允许指针本身定为常量。常量指针(const pointer)必须要初始化。
int a = 0;
int* const p = &a; // p是一个常量指针,指向整型数a
const double pi = 3.14159;
const double* const pPi = π // pPi是一个指向常量对象pi的常量指针
是不是看着有点头晕,理清这些关系的最好方法是从右往左读。
拿上面的第四个来解释一下。离pPi最近的是const,说明pPi是一个常量对象。对象的类型是什么呢,继续往左读,对象的类型由声明符的其余部分所决定的,声明符的下一个是*, 意思是pPi是一个常量指针。该声明语句的基本数据类型(double)确定了常量对象(pPi)指向了一个double型对象。再往左读还是个const,说明这个double型对象也是个常量。
C++ 中有两个术语对指针是否是常量,以及指针指向的对象是否是常量加以区分。
顶层const(top-level const) 表示指针本身是个常量。
底层const(low-level const) 表示指针所指的对象是一个常量。
这两个问题是独立的。
const的常见用法
- const可以用来定义常量。但它更大的魅力是它可以修饰函数的参数、返回值,甚至函数的定义体。这也是实际应用中用的比较多的。
use const whenever you need
- const修饰函数参数。const修饰参数,是防止传入的参数被意外的修改。前面讲到过,可以用引用或者指针作为参数来作为输出。 这样是不需要加const的。其他一些情况,如果传入的参数不需要被改变,则需要加const修饰。基本类型一般是不需要const进行修饰的,因为它采取值专递的方式。如果是用户自定义类型,我们一般采用引用传递,像 Function(A& a),其中A是用户自定义的类型。 Function(A a)的效率不如Function(A& a),因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。但这样声明有一个缺点,它会使调用者误认为出入的引用时可修改的,解决的方法就是加const进行修饰。
如果传入的参数不需要更改,那么加const修饰在实际编程中几乎是必须的。
- const修饰函数的返回值。const一般用来修饰以"指针传递"方式的函数返回值,它表示函数的返回值(即指针)的内容是不可修改的。该返回值只能被赋给加const修饰的同类型指针。修饰以"值传递"方式的函数返回值是没有意义的。
const char* GetStr();
const char* someStr = GetStr(); // 正确
char* str2 = GetStr(); // 错误
- const修饰成员函数。 任何不会修改数据成员的函数都应该声明为const类型。确切的说,const是修饰this指向的对象的。
class A
{
private:
int num; // 成员变量
public:
void Func(int x); // 其实原型是两个参数 Func(A* const this, int x)
void Func2(int y) const;// 原型是Func2(const A* const this, int y)
};
void A::Func(int x)
{
num = num * x; // 正确:this所指向的对象没有const修饰,可以更改
}
void A::Func2(int y) const
{
num = num * y; // 错误:this所指向的对象被const修饰,无法更改这个对象的数据。
}
至于const为什么放在后面,想必各位看官已经猜到了。原型的第一个参数是被省略了,无法修饰,大概也只能放到函数的后面了,虽然看起来怪怪的。
0x01: define
准确的说应该是#define。 它继承自C语言,是一个预处理指令。所谓的预处理,就是在编译之前执行一段程序,可以部分的改变我们写的程序。 之前我们见过的#include就是一条预处理命令,它将所包含的文件替换到所用指令的地方。#define指令是用来把一个名字定义预处理变量的。预处理变量有两种状态:已定义与未定义。#define是用来定义它的。与之对应的是另外两个指令分别检查预处理变量是否已经定义。#ifdef 当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查为真,则执行后续的操作直到遇到#endif 指令为止。
预处理变量无视C++语言中关于作用域的规则
#define 的常见用法
- 头文件保护(header guard)。在定义一个头文件时,建议习惯性地加上头文件保护,没必要太在乎你的程序到底需不需要。这是一个比较好的编程习惯。 一般预处理变量名我们用所要定义的类名的大写来命名。示例如下:
#ifndef _FORM_RIDE_H_
#define _FORM_RIDE_H_
// 你的代码
#endif // _FORM_RIDE_H_
加了头文件保护,别人就不用担心是否重复引入你的文件了。
- 改变程序执行流程。 平台的差异性,客服端和服务器的差异性,有时候为了让代码具有更好的兼容性,通常定义这些宏来执行不同的逻辑代码。这时候只需要一个总的控制文件,就能让代码在不同的情形下执行不同的逻辑。
#ifdef _CLIENT_
if (pEntity == NULL)
{
return false;
}
string pathname = string(pEntity->GetCore()->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_STUB_)
string pathname = string(pKernel->GetResourcePath()) + "share/rule/ridestrength/ride_strength_info.xml";
#elif defined(_SERVER_MEMBER_) || defined(_SERVER_ROOM_)
string pathname = string(pKernel->GetResourcePath()) + "ini/rule/ridestrength/ride_strength_info.xml";
#endif
- 充当函数的作用。说是充当函数,其实和函数还是有挺大差别。只是在用到的地方进行替换,使程序看起来更简洁。下面的代码是用来检查循环的,可以避免死循环。预设一个循环最大值,然后调用这个宏让它检查。
#include <iostream>
using namespace std;
int g_nMaxCirculateCount = 100;
void SetMaxCirculateCount(int count)
{
if (g_nMaxCirculateCount != count)
{
g_nMaxCirculateCount = count;
}
}
#define LoopBeginCheck(name) \
int nCheck##name##Count = 0;
#define LoopDoCheck(name) \
nCheck##name##Count++;\
if((g_nMaxCirculateCount > 0) && (nCheck##name##Count > g_nMaxCirculateCount))\
{\
cout << "文件路径:" << __FILE__ << endl \
<< "函数:" << __FUNCTION__<< endl \
<< "所在行:" << __LINE__ << endl \
<< "循环次数:" << nCheck##name##Count << endl; \
break;\
}
int main()
{
int n = 77 * 88; // 实际需要执行的循环次数
SetMaxCirculateCount(10); // 设置的最大循环次数
LoopBeginCheck(var);
for (int i = 1; i < n; i++)
{
LoopDoCheck(var);
cout << "循环结束" << endl;
}
return 0;
}
这里的 \ 起链接换行的作用,##name## 是拼接参数。 __FILE__等则是一些内置宏。
0x02: static
static定义的变量存储在静态数据区,在静态数据区,内存中所有的字节默认值都是0x00。
静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。与全局变量不同的是,静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。
static的主要有以下三个特性:
表示代码退出一个块后,仍然能够存在的局部变量。因为数据会存储在静态数据区。
用来表示不能被其它文件访问的全局变量和函数。
表示属于一个类而不是属于此类的任何特定对象的变量和函数。这个和Java中static关键字的意义相同。
有了static关键字,变量就变的有些复杂了。是时候理清一下各类型变量的作用域范围了。
常见的变量分为如下几种:全局变量、静态全局变量、静态局部变量和局部变量。
全局变量。存储:静态存储区域。 作用域:在整个工程文件内都有效。
静态全局变量。存储:静态存储区域。 作用域:在定义它的文件内有效。
静态局部变量 存储:静态存储区域。 作用域:只在定义它的函数内有效。程序仅分配一次内存,函数返回后,改变量不会消失。
局部变量 存储:内存栈中。 作用域:只在定义它的函数内有效。程序返回后局部变量被回收。
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
#include <iostream>
using namespace std;
int n = 100; // 全局变量
static int m; // 静态全局变量
class MyClass
{
public:
void ChangeX(int x); // 普通成员函数
static void ChangeY(int y); // 静态成员函数
int GetY(); // 获取全局变量的值
private:
int m_x; // 普通成员变量
static int m_y; // 静态成员变量
};
void MyClass::ChangeX(int x)
{
++m_x;
static int times = 0;
++times;
cout << "第" << times << "次调用该函数" << endl;
}
void MyClass::ChangeY(int y)
{
//m_x = y; // 报错:静态成员函数只能引用静态成员变量
m_y = y;
}
int MyClass::GetY()
{
cout << "静态成员变量y的值为:" << m_y << endl;
return m_y;
}
int MyClass::m_y = 0;//定义并初始化静态数据成员
int main()
{
n = 88;
cout << "全局变量改为" << n << endl;
cout << "静态全局变量的初始值为" << m << endl;
MyClass cls1; // 创建第一个类
cls1.ChangeY(111); // 改变了静态成员的值
MyClass cls2; // 创建第一个类
cls2.ChangeX(1); // 连续调用3次
cls2.ChangeX(2);
cls2.ChangeX(3);
cls2.GetY(); // 直接输出静态成员变量看看
return 0;
}
输出结果:
全局变量改为88
静态全局变量的初始值为0
第1次调用该函数
第2次调用该函数
第3次调用该函数
静态成员变量y的值为:111
输出结果印证了上面所讨论的内容。
0x03: 结束语
const define static在实际编程中应用的非常多,各位看官应多加理解,灵活应用。