C/C++程序员C++

C++入门系列博客四 const define static关键

2016-07-09  本文已影响265人  AceTan

C++ 关键字 Const Define Static


作者:AceTan,转载请标明出处!


今天来讨论一下在C++中很常见的三个关键字 Const Define Static.

0x00: const

const 限定符:有时候我们需要定义这样一种变量,它的值是不可改变的。这时候,我们就需要用到const这个关键字了。

const 关键字在各大考试和C++笔试中经常遇到。比如下面这一道面试题:

说出const关键字在下列语句中的作用。

  1. const int a;
  2. int const a;
  3. const int *a;
  4. int const *a;
  5. int *const a;
  6. const int * const a;
  7. const char* func1();
  8. 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的常见用法

use const whenever you need

如果传入的参数不需要更改,那么加const修饰在实际编程中几乎是必须的。


const  char* GetStr();
const char* someStr = GetStr();        // 正确
char* str2 = GetStr();                // 错误

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 的常见用法


#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的主要有以下三个特性:

有了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在实际编程中应用的非常多,各位看官应多加理解,灵活应用。

C++
上一篇 下一篇

猜你喜欢

热点阅读