从C语言过渡到C++之const

1. 定义常量

1.1 C语言中定义常量的方法

在C语言从零开始这个系列中,我们讲了C语言定义常量的方法。没有看过的同学请参考:C语言从零开始(五)-常量&变量

为什么要定义常量我就不再赘述了,这里重点说说这么定义有什么不好。经常有这样的面试题:请写出下面这段代码的执行结果:

#include <stdio.h>

#define SUM 5 + 1;

void main()
{
  int a = 2 * SUM;
  printf("%d", a);
}

经常有人答12,其实结果是11。不信你用计算机运行一下试试。

为什么会错呢,因为#define定义的常量是伪常量,它在参加编译时做的是原样字符替换。就是2 * SUM这句在编译器看来应该是

int a = 2 * 5 + 1;

如果你的本意是想得到12,那么定义中应该这么写:

#define SUM (5 + 1);

这样的经典错误很多人都犯过,虽然道理大家都知道,但是总会因为粗心大意掉进这个坑里。

于是,C++引入const常量彻底解决了这个问题。后来部分C语言的编译器也开始支持const的使用,这就充分说明了它的价值。

1.2 const常量

在C++中,我们用下面的形式定义常量:

const int MONTH = 12;
const int SUM = 5 + 1;

严格意义上讲,const常量应该叫做“常变量”,它定义了一个值不会被修改的变量。

为了代码风格统一,我们依然习惯把const常量用全大写字母命名。

特点

const常量与普通常量最大的不同有两点:

值不能改变
可以用作数组大小的定义
例如:

const int MAX = 10;
int arr[MAX] = {0};
for (int i = 0; i < MAX; i++)
{
  // Do something
}

1.3 作用范围

const定义的常量的作用域类似与static,只能被当前文件访问。如果想在其他文件中使用该如何写呢?

// file1
const int MAX = 10;
// file 2
extern const int MAX;

不过并不推荐这么使用,还是建议大家把const定义写在头文件中,在需要的文件中包含这个头文件。

2. 指针与const

const的修饰特点是修饰离它最近的部分。它一般有两种用法。

2.1 指向const变量的指针

让指针指向一个const对象,防止指针修改所指向的值。

int age = 30;
const int* ptr = &age;

这段代码定义了一个指针ptr,它指向一个const int类型的数据,不可修改。

  *ptr += 1;  // 报错
  cin >> *ptr;  // 报错

这两种写法都是非法的。

注意:依然可以用 age变量修改。

2.2 const指针

将指针本身声明为一个常量,防止指针位置改变

int a = 3;
int* const p = &a;

p++; // 错误

注意:只有const指针能够指向const变量,例如:

const int a = 9;
const int* p = &a;   // 正确
int* ptr = &a;     //错误

特殊使用:

const int* const p = &a;

这句话的意思是指针变量和指向的地址中的内容都不可变

3. 函数与const

3.1 const参数

如果希望参数在函数内部不被修改,可以用const修饰,如下:

void fun(const int a)
{
  a++; // 非法操作
}

由于a被const修饰为常变量,因此再对它进行a++操作就会报错。

这种写法的目的只是为了限制参数在函数内部的修改,如今越来越多的人喜欢这样实现:

void fun(int a)
{
  const int& b = a;
  b++; // 非法操作
}

效果是完全一样的。

3.2 const返回值

如果函数返回值是一个基本数据类型,用const修饰是没有意义的。比如:

const int fun()
{
  return 1;
}

fun()函数的返回值是不可能做“左值”再被修改的,因为没人会这么使用:

fun() = 2;
编译器也会把这种写法先过滤掉。

一般,const只用来修饰返回值是一个类的对象的函数。例如:

class A
{
public:
  A()
  {
    m_i = 0;
  }

  A(int i) : m_i(i){}

  void Modify(int i)
  {
    m_i = i;
  }

private:
  int m_i;
};

A GetA()
{
  return A(1);
}

const A GetConstsA()
{
  return A(1);
}

void Update(A& a)
{
  a.Modify(2);
}

void Update2(const A& a)
{
  A m = a;
  m.Modify(2);
}

int main()
{
  GetA() = A(1);    // 正确
  GetA().Modify(5);  // 正确

  GetConstsA() = A(1);   // 报错
  GetConstsA().Modify();  // 报错

  Update(GetA());      // 正确
  Update(GetConstsA());  // 报错
  Update2(GetConstsA());  // 正确

  return 0;
}

能看懂其中的奥秘吗?总结一下,const修饰的返回值如果是类的对象,那么:

这个返回值不能做左值(放在等号左边被赋值或者调用其成员函数)
这个返回值的别名必须也被const修饰

4. 举一反三

知道了一般参数和返回值被const修饰的情况,我们应该能够推导出const修饰指针参数和返回值的情况。我们用一段代码来看看容易出现的错误。

void fun1(int* p)
{
  // Do nothing
}

void fun2(const int* cp)
{
  *cp = 3; // 错误
  int i = *cp;
  int* ip2 = cp; // 错误
}

const char* fun3()
{
  return "result of function fun3()";
}

const int* const fun4()
{
  static int i;
  return &i;
}

int main()
{
  int x = 0;
  int* p = &x;
  const int* cp = &x;

  fun1(p);
  fun1(cp); // 错误

  fun2(p);
  fun2(cp);

  char* cp = fun3();  // 错误
  const char* ccp = fun3();

  int* p2 = fun4(); // 错误
  const int* const ccp = fun4();
  const int* cp2 = fun4();
  *fun4() = 1; // 错误

  return 0;
}

这段程序的各种赋值其实完全符合第2部分中介绍的原则。在传参和赋值的过程中需要注意:

指针内容被const修饰时,*p不可修改
指针内容被const修饰时,不能赋值给内容非const的指针
指针变量和内容都被const修饰时,只能给相同情况的指针赋值
说起来有些拗口,仔细想想其实和第二部分所讲的内容相似。

OK,今天就先到这里。

(0)

相关推荐

  • C语言实现C++继承和多态的代码分享

    这个问题主要考察的是C和C++的区别,以及C++中继承和多态的概念. C和C++的区别 C语言是面向过程的语言,而C++是面向对象的过程. 什么是面向对象和面向过程? 面向过程就是分析解决问题的步骤,然后用函数把这些步骤一步一步的进行实现,在使用的时候进行一一调用就行了,注重的是对于过程的分析.面向对象则是把构成问题的事进行分成各个对象,建立对象的目的也不仅仅是完成这一个个步骤,而是描述各个问题在解决的过程中所发生的行为. 面向对象和面向过程的区别? 面向过程的设计方法采用函数来描述数据的操作,

  • C语言中strlen() strcpy() strcat() strcmp()函数的实现方法

    strlen函数原型:unsigned int strlen(const char *);返回的是字符串中第一个\0之前的字符个数. 1.strcat函数原型char* strcat(char* dest,const char* src); 进行字符串的拼接,将第二个字符串连接到第一个字符串中第一个出现\0开始的地方.返回的是拼接后字符的首地址.并不检查第一个数组的大小是否可以容纳第二个字符串.如果第一个数组的已分配的内存不够容纳第二个字符串,则多出来的字符将会溢出到相邻的内存单元. 2.str

  • C语言实现2048小游戏

    本文实例为大家分享了C语言实现2048小游戏的具体代码,供大家参考,具体内容如下 具有以下特点: 1.linux下完成 2.非堵塞键盘读取 3.随机生成2和4 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define TTY_PATH "/dev/tty" #define STTY_ON "stty raw -echo -F" #define STTY_O

  • C语言去除相邻重复字符函数的实现方法

    C语言去除相邻重复字符函数的实现方法 字符去重函数 功能:去重字符串相邻重复的字符,不相邻的不用去重 参数: arg1 -- 输入字符串 arg2 -- 字符串开始位置 arg3 -- 字符串结束位置 要求: 输入参数为arg1时, 对这个字符串去重 输入参数为arg1,arg2时, 从arg2位置到字符串结束,去重 输入参数为arg1,arg2,arg3时,从arg2到arg3位置,去重 src/include/catalog/pg_proc.h DATA(insert OID = 6669

  • 从C语言过渡到C++之引用(别名)

    今天要讲的是C++中我最喜欢的一个用法--引用,也叫别名. 引用就是给一个变量领取一个变量名,方便我们间接地使用这个变量.我们可以给一个变量创建N个引用,这N + 1个变量共享了同一块内存区域. 1. 声明引用 创建引用的格式如下: 数据类型 引用名 = 原变量 比如: int a = 1; int& b = a; 在这段代码中,我们给变量a创建了一个别名b.它们公用同一块内存区域,就是创建变量a时申请的区域. 注意:由于引用并不需要申请一块新的内存空间,因此在建立引用时只能声明,不能定义. 面

  • 从C语言过渡到C++之基本变化

    说到C++和C语言的区别,大部分人都会想到面向对象和面向过程.然而这种说法并不准确.面向对象和面向过程指的是两种不同的程序设计思想,而C++与C是两种编程语言,难道C++就不能用于面向过程去解决问题吗,当然可以.而面向对象的设计思想也可以用到C语言中去,我之前的文章就涉及过这方面的知识. 我们这个系列就是要抛开编程思想,单纯地从语法的角度介绍一下C++中究竟加入了哪些C语言中没有的功能.希望大家在掌握了C语言之后再来学习这部分内容. 首先,让我们看一段标准的C++代码: // main.cpp

  • C语言模拟实现atoi函数的实例详解

    C语言模拟实现atoi函数的实例详解 atoi函数,主要功能是将一个字符串转变为整数,例如将"12345"–>12345.但在实现过程中,我们难免会因为考虑不够全面而漏掉比较重要的几点,今天就总结一下实现atoi函数需要注意的地方. 1.指针为NULL 2.字符串为空字符串 3.空白字符 4.正号与负号问题 5.溢出问题 6.异常字符处理 接下来看代码:(具体几种问题处理都在代码的注释中说明) #define _CRT_SECURE_NO_WARNINGS 1 #include

  • C语言实现静态顺序表的实例详解

    C语言实现静态顺序表的实例详解 线性表 定义一张顺序表也就是在内存中开辟一段连续的存储空间,并给它一个名字进行标识.只有定义了一个顺序表,才能利用该顺序表存放数据元素,也才能对该顺序表进行各种操作. 接下来看看静态的顺序表,直接上代码: SeqList.h #define _CRT_SECURE_NO_WARNINGS 1 #ifndef __SEQLIST_H__ #define __SEQLIST_H__ #include <stdio.h> #include <stdlib.h&g

  • C语言实现俄罗斯方块小游戏

    C语言实现俄罗斯方块小游戏的制作代码,具体内容如下 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define TTY_PATH "/dev/tty" #define STTY_ON "stty raw -echo -F" #define STTY_OFF "stty -raw echo -F" int map[21][14]; char

  • 从C语言过渡到C++之const

    1. 定义常量 1.1 C语言中定义常量的方法 在C语言从零开始这个系列中,我们讲了C语言定义常量的方法.没有看过的同学请参考:C语言从零开始(五)-常量&变量 为什么要定义常量我就不再赘述了,这里重点说说这么定义有什么不好.经常有这样的面试题:请写出下面这段代码的执行结果: #include <stdio.h> #define SUM 5 + 1; void main() { int a = 2 * SUM; printf("%d", a); } 经常有人答12,

  • C语言详细分析讲解关键字const与volatile的用法

    目录 一.const 只读变量 二.const 全局变量的分歧 三.const 的本质 四.const 修饰函数参数和返回值 五.volatile 解析 六.小结 一.const 只读变量 const 修饰的变量是只读的,本质还是变量 const 修饰的局部变量在栈上分配空间 const 修饰的全局变量在全局数据区分配空间 const 只在编译期有用,在运行期无用 const 修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边. 二.const 全局变量的分歧 在现代C语言编

  • 解析C语言中如何正确使用const

    基本解释 const是一个C语言的关键字,它限定一个变量不允许被改变.使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助.虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题. 问题: const变量 & 常量为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?const int n = 5;int a[n]; 答

  • C++中const的实现细节介绍(C,C#同理)

    1.什么是const?  常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的.(当然,我们可以偷梁换柱进行更新:) 2.为什么引入const?  const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点. 3.cons有什么主要的作用? (1)可以定义const常量,具有不可变性. 例如:  const int Max=100; int Array[Max]; (2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患.例如:

  • 利用vue-i18n实现多语言切换效果的方法

    前言 有些项目我们需要支持多种语言切换,满足国际化需求. vue-i18n是一个vue插件,主要作用就是让项目支持国际化多语言,使用方便快捷,能很轻松的将我们的项目国际化.本文主要介绍使用vue-i18n实现切换中英文效果. 安装vue-i18n 我们使用npm安装vue-i18n. npm install vue vue-i18n --save 引入vue-i18n 首先在 main.js 中引入 vue-i18n. import Vue from 'vue' import App from

  • C语言常量介绍

    目录 什么是常量 常量都有哪些 这四种常量的特点.注意事项等等 1.字面常量: 2.const修饰的常变量 3.#define定义的标识符常量 4.枚举常量 总结 什么是常量 从字面上简单解释就是不变的量叫常量 常量都有哪些 字面常量 const修饰的常变量 #define定义的标识符常量 枚举常量enum 这四种常量的特点.注意事项等等 1.字面常量: 字面常量就是直接写出来的量: 有字符.数字.字符串等字面常量: 2.const修饰的常变量 开门见山:被const修饰的变量就不能在改变了,具

  • JavaScript代码简化技巧实例解析

    函数式编程可以使您的代码更简单.简单意味着代码易于阅读和理解,可测试和可维护. 在本文中,我描述了一些函数式编程(FP)技巧,您可以使用它们来简化代码,从而使代码更好. 摆脱临时变量和不变性原则 函数式编程倾向于不变性,这种不变性原则意味着在初始化变量之后不会更改它们的值.同样,创建对象或字符串后,您也无需对其进行突变. 如果使用JavaScript编程,则所有变量定义都应使用 const.对于正在阅读您的代码的任何人,常量定义都可以让您高枕无忧:它保证永远不会重新分配变量,因为重新分配是不可能

  • 深入C# 4.0 新特性dynamic、可选参数、命名参数的详细介绍

    1.dynamic ExpandoObject熟悉js的朋友都知道js可以这么写 : 复制代码 代码如下: var t = new Object(); t.Abc = 'something'; t.Value = 243; 现在这个js动态语言的特性,我们也可以在c#中使用了,前提是将一个变量声明为ExpandoObject类型.如下例: 复制代码 代码如下: static void Main(string[] args) { dynamic t = new ExpandoObject(); t

随机推荐