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

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

  1. RecBole小白入门系列博客(二) ——General类模型运行流程

    RecBole小白入门系列博客(二) --General类模型运行流程 写在前面 选定模型 设置模型超参数 选定数据集 数据集基本格式 设置数据集参数 设置训练参数 设置评测参数 总结参数设置 运行 ...

  2. 2021-08-26 转载 Scala快速入门系列博客文章

    作者:Huidoo_Yang 出处:http://www.cnblogs.com/yangp/ 本文版权归作者Huidoo_Yang和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面 ...

  3. 点云深度学习系列博客(四): PointNet代码精讲

    目录 1. 代码解析 1.1 初始化 1.2 数据载入 1.3 模型载入 1.4 训练代码 2. 实验结果 Reference 最近开始上手点云深度学习项目,相比之前纸上谈兵的阶段,此时我将把更多的精 ...

  4. 项目体系架构设计——基于Spark平台的协同过滤实时电影推荐系统项目系列博客(四)

    系列文章目录 初识推荐系统--基于Spark平台的协同过滤实时电影推荐系统项目系列博客(一) 利用用户行为数据--基于Spark平台的协同过滤实时电影推荐系统项目系列博客(二) 项目主要效果展示--基 ...

  5. graphviz 画决策树_数据挖掘入门系列教程(四)之基于scikit-lean决策树处理Iris

    数据挖掘入门系列教程(四)之基于scikit-lean决策树处理Iris 加载数据集 数据特征 训练 随机森林 调参工程师 结尾 数据挖掘入门系列教程(四)之基于scikit-lean决策树处理Iri ...

  6. SpringBoot和Vue集成Markdown和多级评论——基于SpringBoot和Vue的后台管理系统项目系列博客(二十三)

    系列文章目录 系统功能演示--基于SpringBoot和Vue的后台管理系统项目系列博客(一) Vue2安装并集成ElementUI--基于SpringBoot和Vue的后台管理系统项目系列博客(二) ...

  7. Python Web企业门户网站—系列博客教程介绍

    本系列教程将使用Python Web的Django框架实现企业门户网站的制作.本教程一共分为8篇博文,将从最基础的Python语法讲起,然后使用Django3逐步搭建一个完整的企业门户网站项目实例.如 ...

  8. 【云计算】云上建站快速入门:博客、论坛、CMS、电子商务网站统统

    免费网站怎么建,空间也能免费吗? 免费网站怎么建立,免费网站并非免费空间 互联网真的有免费建站这等好事? 现在制作一个网站已经越来越容易了,只要知道清晰的流程之后都是可以很快的建好一个企业或者个人网站 ...

  9. LINQ之路系列博客后记

    缘起 今年3月,我换了工作单位.后来多次收到公司的新人培训邮件,不过对此我并不感冒,说实话并不喜欢这种活动.印象中,新人培训无非是唠叨些公司的规章制度.侃述一下公司的光辉历史还有灿烂的未来发展等等.规 ...

  10. flutter text 自动换行_Flutter 系列博客——05 StatelessWidget vs StatefulWidget

    前言 上一篇我们对 Flutter UI 有了一个基本的了解. 这一篇我们通过自定义 Widget 来了解下如何写一个 Widget? 然而 Widget 有两个,StatelessWidget 和 ...

最新文章

  1. oracle 撤销回退,Oracle 回滚(ROLLBACK)和撤销(UNDO)
  2. 制造工业中的机器学习应用:I概览
  3. ORA-600[4194]/[4193]解决
  4. ThreadLocal用法详解和原理
  5. linux局部变量特殊字符替换,变量,全局变量,环境变量,特殊符号、管道符命令:cut、sort、uniq、wc、tee、tr、sp...
  6. 新代系统cnc怎样连接电脑_你真的了解3C产线上的运控系统吗?
  7. JAVA设置输入数据范围,如何使用Apache POI(SXSSF)为特定单元格设置数据(数字)格式区域设置?...
  8. 【SSH进阶】No result defined for action **的解决方案
  9. Android 使用Nginx rtmp 模块
  10. 凸优化学习笔记(三):凸优化问题
  11. ARM指令集(数据处理指令)
  12. warning: Clone succeeded, but checkout failed.
  13. IIS 部署.NetCore
  14. javafor循环基础练习 -26个大小写字母
  15. 【谈判】——如何在博弈中获得更多
  16. 微信网页中点击图片放大
  17. 蓝牙耳机什么牌子的好?口碑、销量双高的十大蓝牙耳机品牌!
  18. JavaScript实现动态添加员工信息
  19. 五金机电行业智能供应链管理系统解决方案:数智化供应链为传统产业“造新血”
  20. 关于使用Cytoscape软件合并多个网络图

热门文章

  1. axis2与cxf区别
  2. STM32串口通信(使用C8T6)
  3. Linux sort命令浅析
  4. AS3 库资源 很多非常有用的类库
  5. js开源框架最新版下载
  6. Java 信号量 Semaphore 介绍
  7. 【Alpha】Scrum Meeting 1
  8. 基于 Laravel 的模块化开发框架 Notadd RC1 fix1 发布
  9. Cloudera Manager 安装 CDH5.x 心得
  10. JDBC学习总结4-------简化DAO的写法