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

C++ 关键字 Const Define Static

今天来讨论一下在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的常见用法

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值