深入聊聊C语言中的Const关键字

目录
  • 前言
  • 01const简述
  • 02常量的应用
    • 常量作为函数的参数
    • C++中应用加const
  • 03#define和const
  • 总结

前言

const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程序上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解别人的程序有所帮助。

01const简述

下面简单描述一下const,基本都是教科书的知识。const修饰的变量,其值存放在只读数据段中,其值不能被改变。称为只读变量。关于什么是数据段,什么是代码段,请看我之前的文章《C语言的内存分配》。

int const a;
const int a;

上面两条语句都可以将a声明为一个整数,它的值不能被修改。这两种方式你可以任意选一种即可。

常量在定义时可以被初始化。

int const a =15;

当指针和常量结合时,就会很有趣,因为有两样东西都可能成为常量,指针和它指向的实体。一般大家在大学考计算机二级和面试时经常会遇到的。

int *a;

a就是一个很普通的指向整型的指针。

int const *a;

这时则是一个指向整型常量的指针。也就是说,你可以修改指针的值,但是不能修改它指向的值。

int *const a;

这时a是一个指向整型的常量指针。这个指针是常量,它的值无法修改,但是你可以修改它所指向的整型的值。

int const *const a;

这个时候无论是指针本身还是它所指向的值都是常量,都不允许修改。

那么问题来了,就像C语言的运算符的优先级,这个东西很不好记忆,在实际开发中,我们直接多用()符号解决优先级的问题。上面指针和const结合那么麻烦,学习为了什么呢?

1、合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

2、正是基于上面的原因,一些优秀的开源代码都会利用const这个属性,深入理解后,方便我们阅读理解一些优秀的开源代码。

02常量的应用

上文就简述了一下教科书中的const定义,现在说一下const在我日常开发中的应用。

在单片机开发中

const定义一个常量,在单片机开发中,一个定义在函数体外的常量constint a = 5; 它是存储在单片机内部Flash里的,不懂的同学请看之前的文章《C语言在STM32中的内存分配》。那么上文提到和指针结合时,也是存储在内部Flash中吗?我们来验证一下

int data = 0x1234;
int const *a = &data;
int *const b= &data;
int const *const c= &data;
int main(void)
{
  int data1 = 0x1234;
  a =&data1;
  data1 = *b;
  data1 = *c;
  while(1);
}

它们的内存分配如下

b和c是分配到内部flash的,a是分配到ram中的。其实这也很好理解,根据上面的const的定义,单片机在分配时,将不能修改的变量,也就是只读变量放到flash中,可以读写的变量放到ram中,这个大家仔细想一下就明白了。

常量作为函数的参数

非指针参数(也就是传值参数)不会被修改原始值,const对它是没有意义的,所以这里只讨论参数是指针加const的情况。

在上面看到,指针加const共3种情况,这里先讨论int const *a; 也就是你可以修改指针的值,但是不能修改它指向的值。

int fun(int *p)
{
  if(*p == 0xA5)
  {
    return*p;
  }else{
    p++;
    return *p;
  }
}

上面是个简单的例子,也就是传入一个指针,函数读取指针指向的内容,执行不同的命令。类似串口接收,一个函数内部处理这些数据,但是不能修改,可能串口接收的数据在其他地方还有用。

在上述例子中,没有问题的,因为代码全在“掌控”中。函数内部是否进行写操作,自己是知道的。但还有一个更规范的写法。

int fun(int const *p)
{
  if(*p == 0xA5)
  {
    return *p;
  }else{
    p++;
    return *p;
  }
}

这里写法,就是明显表现出自己的设计意图,函数内部不可以对指针指向的内容进行修改,只能读取。

如果尝试修改,编译器会直接报错的。但是函数内部也是可以绕过去,修改的数据的,如下

int fun(const int *p)
{
  int *p2 = p; /* 来个重名指针会绕过const的限制*/
  *p2 += 1;
  return*p;
}

那么对于int *const a;有没有对应的使用场景呢?如下

这样的接口设计,如果函数内部尝试修改指针的值,也就是指针指向的位置,编译器就会直接报错。

不过这里例子很现实,因为即使去掉p2的const修饰,编译器会直接报waring,因为p2是入参。这里只是简单举例子,大家理解意思就好。

在日常开发中,入参是intconst *a; 使用场景比较多。

C++中应用加const

C++中可以使用应用的语法,这里不再展开什么是应用,如下例子,C++函数参数中引用时也常加const修饰,如下

void find(constint  &x)
{
  .......
}

最后,举两个常用的标准C库函数声明,它们都是使用const的典范。

1.字符串拷贝函数:char*strcpy(char*strDest,constchar *strSrc);

2.返回字符串长度函数:intstrlen(constchar *str);

03#define和const

#define预编译和const在某些情况下有些“混淆”,如下

#define MAX_NUM 5
int const max_num = 5;
void fun(){
  if(len >MAX_NUM)
  if(len> max_num)
}

上述代码5行和6行都能起效果。那么我们就详细分析一下它们的区别

1、#define的数据是宏定义,它占用的是代码段空间(单片机对应:内部flash),const定义一个数据类型,它占用的是data段(单片机对应:内部ram)。

2、如上,#define是宏定义,在预编译阶段直接替换,而const是数据类型。

#define MAX_NUM 5
int const max_num = 5;
int data[MAX_NUM];
intdata2[max_num];

上述代码第4行是编译不过的,因为max_num是一个int的数据类型变量,数组定义的长度不能用变量。实际上,在更章节第一个例子,只用于判断长度,#define更加合适,因为只要允许使用字面值常量的地方都可以使用宏定义。

3、define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。define只是简单的字符串替换会导致边界效应,

比如定义

#define A 1
#define B A+3
#define C A/B3

那么c是多少呢?c=A/B3=A/A+33=1/1+33=10;所以这种用的时候可以直接都用个括号括起来,就不怕边界效应了。

4、const不能重定义,不可以定义两个一样的,而define就比较牛气了,它通过undef取消某个符号的定义,再重新定义。并还可以用于判断宏定义是否存在,常用于头文件防止头文件被重复引用。

#ifndef GRAPHICS_H //防止graphics.h被重复引用
#defineGRAPHICS_H
……代码……
#endif

5、const常量可以进行调试的,define是不能进行调试的,主要是预编译阶段就已经替换掉了,调试的时候就没它了。

总结

到此这篇关于C语言中Const关键字的文章就介绍到这了,更多相关C语言的Const关键字内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C语言中const关键字的用法

    关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变,我想一定有人有这样的疑问,C语言中不是有#define吗,干嘛还要用const呢,我想事物的存在一定有它自己的道理,所以说const的存在一定有它的合理性,与预编译指令相比,const修饰符有以下的优点: 1.预编译指令只是对值进行简单的替换,不能进行类型检查 2.可以保护被修饰的东西,防止意外修改,增强程序的健壮性 3.编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编

  • 详解C++中的const关键字及与C语言中const的区别

    const对象默认为文件的局部变量,与其他变量不同,除非特别说明,在全局作用域的const变量时定义该对象的文件局部变量.此变量只存在于那个文件中中,不能别其他文件访问.要是const变量能在其他文件中访问,必须显示的指定extern(c中也是)   当你只在定义该const常量的文件中使用该常量时,c++不给你的const常量分配空间--这也是c++的一种优化措施,没有必要浪费内存空间来存储一个常量,此时const int c = 0:相当于#define c 0:    当在当前文件之外使用

  • C语言关键字const和指针的结合使用

    我们先定义三个变量 1.const int *p1 2.int const *p2 3.int *const p3 p1.p2.p3这三个指针都是指向int类型的,那它们有什么区别呢 写个代码测试一下 编译一下 可看到第11,12,16行报错,从中可得出以下结论: const int * 与 int const *是一样的效果,指向的内存是不能改变的,即指针指向的内容是只读的,或者说是一个常量.不过指向的位置是可以更改的,即p1和p2可以重新指向别的常量. 而char *const 刚好相反,表

  • 总结C语言中const关键字的使用

    什么是const? 常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的.(当然,我们可以偷梁换柱进行更新:) 为什么引入const? const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点. const关键字使用非常的灵活,这一点和php差别很大,php中const用来在类中定义一个常量,而在c中,const因位置不同有不同的作用,因情景不同有不同的角色,使用起来也是非常的灵活. (1):const用来修饰普通的变量(指针变量除外)的时

  • 深入聊聊C语言中的Const关键字

    目录 前言 01const简述 02常量的应用 常量作为函数的参数 C++中应用加const 03#define和const 总结 前言 const是一个C语言的关键字,它限定一个变量不允许被改变.使用const在一定程序上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解别人的程序有所帮助. 01const简述 下面简单描述一下const,基本都是教科书的知识.const修饰的变量,其值存放在只读数据段中,其值不能被改变.称为只读变量.关于什么是数据段,什么

  • 深入了解C语言中的const和指针

    目录 前言 指针的赋值 问题 ANSI C 有关简单赋值的标准 问题解决 const修饰 const修饰变量 const修饰指针 前言 文章内容由阅读<C专家编程>整理而来.希望可以帮助大家解决在指针赋值和const方面的问题,也希望大家多多指正文章中的错误,共同进步. 指针的赋值 问题 将一个类型为 char** 的值赋值给一个 const char** 类型的对象是否合法呢? 先说结果,在vs的环境下,编译器不会报错也不会有任何警告. 但在linux环境下用gcc编译就会出现下面的警告:

  • 浅析Go语言中的Range关键字

    前言 相信用过Range的朋友们都知道,Go语言中的range关键字使用起来非常的方便,它允许你遍历某个slice或者map,并通过两个参数(index和value),分别获取到slice或者map中某个元素所在的index以及其值. 比如像这样的用法: for index, value := range mySlice { fmt.Println("index: " + index) fmt.Println("value: " + value) } 上面的例子足够

  • C语言中的const和free用法详解

    注意:C语言中的const和C++中的const是有区别的,而且在使用VS编译测试的时候.如果是C的话,请一定要建立一个后缀为C的文件,不要是CPP的文件.因为,两个编译器会有差别的. 一.C语言中的const比较常见的用法,const做常量 #include<stdio.h> #include<malloc.h> #include<string.h> /* C中的const用法(使用VS测试的时候,要注意建立一个C后缀的文件,因为C的编译器和C++的编译器还是有区别的

  • C语言中的const如何保证变量不被修改

    这小段文章要理清楚的是,在C语言中,const是如何保证变量不被修改的? 我们可以想到两种方式: 第一种,由编译器来阻止修改const变量的语句,让这种程序不能通过编译: 第二种,由操作系统来阻止,即把const 的内存地址访问权限标记为"只读",一旦运行中的程序试图修改它,就会产生异常,终止进程. 上面想到的这两种方式,都能达到让某一变量的值不被修改的目的,那么究竟是哪一种呢?我们写两个例子来看一看. 先来看一个简单的例子,源文件const.c: #include <stdio

  • 聊聊C语言中sizeof运算符的一个陷阱

    sizeof运算符通常用于获取变量或类型所占内存的大小(单位是字节) #include <stdio.h> struct D{ char a; int b; }; int main() { int a = 0; struct D d; printf("sizeof(a)=%ld\n", sizeof(a)); printf("sizeof(int)=%ld\n", sizeof(int)); printf("sizeof(d)=%ld\n&qu

  • Go语言中的Iota关键字

    一.复习常量 提到Iota这个关键字,就必须要复习一下Go语言的常量. 1.Go语言的常量一般使用const声明 2.Go语言的常量只能是布尔型.数字型(整数型.浮点型和复数)和字符串型 3.Go语言的常量可以不指定类型,由编译器自己推断,如下面的[string]就是可以不写的(也称为 隐式类型定义) const s string = "constant" 4.常量不能在程序运行时改变 二.Iota的用法 通过一个具体的事例,来看iota的特性.例下面的代码输出的结果每一个常量的值是什

  • 详解C语言中的Static关键字

    一.static关键字的基本含义 首先,static关键字的意思是静态的,用于修饰局部变量,全局变量和函数,修改其数据储存类型 1.局部变量:在任意一个函数内部定义的变量(不加static),初始值不确定,出函数自动销毁,存放于栈区. 使用static修饰这个变量时,编译器会把她初始化为零,存储于静态区,函数返回时值保持不变,出函数不销毁,下一次进入函数依然存在.根本原因——static修饰的局部变量存储在静态区. 2.全局变量 :普通全局变量定义在函数体外部,在静态区分配存储空间,编译器自动对

  • 聊聊R语言中Legend 函数的参数用法

    如下所示: legend(x, y = NULL, legend, fill = NULL, col = par("col"), border = "black", lty, lwd, pch, angle = 45, density = NULL, bty = "o", bg = par("bg"), box.lwd = par("lwd"), box.lty = par("lty")

  • go语言中的defer关键字

    我是谁 defer - 顾名思义翻译过来叫 延迟, 所以我们通常称呼 defer func() 这样 defer 后面紧跟的函数为 延迟函数. 作者注: 不过从实际应用来讲, 延迟函数通常用来做一些函数最终返回前的一些收尾工作, 所以称呼为收尾函数其实更贴切. 三围几何 defer 有其独特的一面, 了解其个性, 才能更好的下手. 延迟性 顾名思义, 既然叫延迟函数, 那么肯定具备延迟性. 我们来看看怎么个延迟法, defer_defer.go // defer_defer.go package

随机推荐