C语言变量常量,基本数据类型及数据类型转换详讲(三)

简介: C语言变量常量,基本数据类型及数据类型转换详讲

6.2 C99标准

C 语言标准C99提供了_Bool 型,_Bool 仍是整数类型,但与一般整型不同的是,_Bool 变量只能赋值为 0 或 1,非 0 的值都会被存储为 1,C99 还提供了一个头文件 <stdbool.h> 定义了 bool 代表_Bool,true 代表 1,false 代表 0。只要导入 stdbool.h ,就能方便的操作布尔类型了。

📝 下述代码说明 bool类型 用法:

#include<stdio.h>
#include<stdbool.h>
int main()
{
  //bool类型所占字节大小
  printf("bool类型所占字节大小:%lld\n\n", sizeof(bool));
  // false对应整数
  bool a = false;
  printf("false对应整数:%d\n\n", a); //输出说明 false 对应 0
  //true对应整数
  bool b = true;
  printf("true对应整数:%d\n\n", b); //输出说明 true 对应 1
  if (true)
    printf("我曾经很爱很爱你,但再也不会去找你了,夜莺\n\n");
  if (!false)//false取反为true
    printf("爱一个人,九分喜欢,一分尊严\n\n");
  return 0;
}

7.变量与常量

7.1 变量

7.1.1 基本概念

变量相当于内存中一个数据存储空间的表示,可以把变量看做是一个房间的门牌号,通过门牌号可以找到房间,而通过变量名就可以访问到变量值。

7.1.2 三个基本要素:类型 变量名 变量值

不论是使用哪种高级程序语言编写程序,变量都是其程序的基本组成单位。

/*
  类型:int
  变量名:a
  变量值:100
*/
int a = 100;

7.1.3 变量的声明与定义

  • 声明是用来告诉编译器变量的名称和类型,而不分配内存。
  • 定义是为了给变量分配内存,可以为变量赋初值。
#include<stdio.h>
int main()
{
  int a; //变量声明,未分配内存空间
  a = 520; //变量定义,分配内存空间
    int b = 1314; //变量声明并定义(也叫 初始化)
  return 0;
}

7.1.4 变量使用注意事项

  • 变量表示内存中的一个存储区域。不同的变量,类型不同,占用的空间大小也可能不同。
  • 该区域有自己的名称(变量名)和类型(数据类型)
  • 变量必须先声明,再定义使用。
  • 该区域的数据值可以在同一类型取值范围内不断变化。
  • 变量在同一作用域内不能重名。
  • 变量 = 数据类型 + 变量名 + 变量值
  • 数据类型只在定义变量时指明,而且必须指明;使用变量时无需再指明,因为此时的数据类型已经确定了。

7.2 常量

7.2.1 基本概念

  • 常量是固定值,在程序执行期间不能改变。这些固定的值,又叫做字面量。
  • 常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
  • 常量的值在定义后不能进行修改

7.2.2 常量举例

//100就是常量
int a = 100;
// 13.14就是常量
double b = 13.14;
// A就是常量
char c = 'A';

7.2.3 define定义常量

凡是以 # 开头的均为预处理指令,预处理又叫预编译。预编译不是编译,而是编译前的处理。这个操作是在正式编译之前由系统自动完成的。

📍 C 语言中,可以用 #define 定义一个标识符来表示一个常量,用 #define 定义标识符的一般形式为:

#define 标识符 常量值   //注意define最后没有分号
//例如:
#define MAX_VALUE 100       //定义整型变量MAX_VALUE值为100
#define USER_NAME "huge"    //定义字符串变量USER_NAME值为"huge"
#define PI 3.1415926        //定义浮点数变量PI值为3.1415926

📝 举例说明:计算半径为2的圆的面积

#include<stdio.h>
//定义一个常量PI,值为3.14
#define PI 3.14
int main()
{
  int r = 2;
  printf("S = %lf", PI * r * r);
  return 0;
}

7.2.4 const定义常量

关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变,我想一定有人有这样的疑问,C语言中不是有#define吗,干嘛还要用const呢,我想事物的存在一定有它自己的道理,所以说const的存在一定有它的合理性,与预编译指令相比,const修饰符有以下的优点:

  1. 预编译指令只是对值进行简单的替换,不能进行类型检查
  2. 可以保护被修饰的东西,防止意外修改,增强程序的健壮性
  3. 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

📍 基本语法:

const 数据类型 常量名 = 常量值;

📝 举例说明:计算半径为2的圆的周长

#include<stdio.h>
//定义全局常量 PI,值为3.14
const double PI = 3.14;
int main()
{
  //定义局部常量 r ,值为2
  const int r = 2;
  printf("C = %lf", 2 * PI * r);
  return 0;
}

7.2.5 补充:#define与const区别

  1. const定义的常量时带类型,#define不带类型。
  2. const是在编译、运行的时候起作用,而define是在编译的预处理阶段起作用。
  3. #define 只是简单的替换,没有类型检查。简单的字符串替换会导致边界效应。
  4. const常量可以进行调试的,#define是不能进行调试的,主要是预编译阶段就已经替换掉了,调试的时候就没它了。
  5. const不能重定义, 不可以定义两个一样的,而#define通 过undef取消某个符号的定义,再重新定义。
  6. #define 可以配合#ifdef#ifndef#endif 来使用,可 以让代码更加灵活,比如我们可以通过#define来启动或者关闭调试信息。

7.2.6 注意事项和细节说明

  1. 常量不能作为左值。例如:
// 常量100作为右值,是可以的
int a = 100;
// 常量100作为左值,是不可以的,会报错
100 = 20; //将20赋值给100,很明显是一个荒谬的行为
  1. 常量一旦确定,就无法更改。
const int a = 520;
a = 1314; //由于有关键字const修饰,a是常量,因此直接把1314赋值给a会报错。

学到了指针小伙伴可能会提出不同的意见,即使定义为常量,也可以通过指针修改值。

但你必须思考这样一件事情:为什么要定义常量?不就是告诉自己与其他程序员不要去修改这个值吗?只需要将它作为一个常量看待即可。这才是常量存在的意义,是const存在的意义,因此,如果你将一个变量用const修饰,又用指针去修改它,这本身就是一个荒谬的事情!

  1. 常量必须一开始就赋值,即声明时完成初始化。
//声明并初始化是可以的
const int a = 520;
//先声明,后复制是不可以的,如下代码是错误的
const int b;
b = 1314;

8.数据类型转换

数据类型转换就是将数据(变量、数值、表达式的结果等)从一种类型转换为另一种类型。

8.1 自动类型转换

自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生。

在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型转换为左边变量的类型,这可能会导致数据失真,或者精度降低;所以说,自动类型转换并不一定是安全的。对于不安全的类型转换,编译器一般会给出警告。

// 520是int类型常数,因此这里进行了自动类型转换,将int类型的520变成了float类型的520.000000
float a =520;

📍 自动类型转换规则如下:

  1. 运算结果的类型是由参与运算的精度最高的那个数据类型所决定。
    比如说 1/2 ,这两个数都是整型,因此参与运算的最高精度 的数据类型也是整型,因此运算结果也是整型,即会去除小数位,0.5去除小数位就是0,即 1/2 的结果为0。
  2. 参与运算的类型不同,则先转换为同一类型。
int r = 2;
double pi = 3.14;
/*
  1.在计算 pi * r * r时,会先将这三个变量值转换为数据类型一致的值
  2.pi为double,r为int,根据上图可以得到double的精度更高
  3.因此 r 的变量值2也会先转成double类型的2.000000,再进行计算
  4.所以 3.140000 * 2.000000 * 2.000000 得到12.560000
  5.但接收类型为int,因此会舍去小数部分,则s为12
*/
int s = pi * r * r;
  1. 两种类型字节数不同,转换为高字节数的类型
  2. 转换按数据长度增加的方向进行,以保证精度不降低。如int型和double型运算时,先把int量转成double型后再进行运算。
/*
  1. 在运算9.4 / 2前
  2. 发现9.4是double类型(默认),2是int类型
  3. 根据自动转换规则,将int类型的2转成double类型的2.000000
  4. 因此运算的结果为 4.700000
  5. 又发现了接收类型为Int,因此舍去小数部分得到 a 为 4
*/
int a = 9.4 / 2;
  1. 若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型。因此,从这个意义上讲,无符号数的运算优先级要高于有符号数,这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。
  2. 所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
  3. char型和short型参与运算时,必须先转换成int型
char a = 'A';
char b = 'B';
/*
  1.运算 a+b 前,发现a和b是字符型,因此先转为int类型
  2.'A' --> 65   'B' --> 66
  3.所以 a+b 的结果为 131
  4.发现 c 是 char类型,因此又会自动转为131对应的ASCII字符为【问号?】
*/
char c = a + b;
short d = 1;
/*
  1.运算 d+a 前,发现d是short,a是char
  2.因此将这两个都转成int类型
  3.short类型的1转成int类型的1,char类型的a转成int类型的65
  4.所以 d+a 即计算 1+65 --> 为int类型的66
  5.发现接收类型是e,所以e为66,不需要类型转换了
*/
int e = d + a;

8.2 强制类型转换

自动类型转换是编译器根据代码的上下文环境自行判断的结果,有时候并不是那么“智能”,不能满足所有的需求。如果需要,程序员也可以自己在代码中明确地提出要进行类型转换,这称为强制类型转换。

  • 自动类型转换是编译器默默地、隐式地进行的一种类型转换,不需要在代码中体现出来;
  • 强制类型转换是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。

换句话说,自动类型转换不需要程序员干预,强制类型转换必须有程序员干预。

📍 强制类型转换的格式为:(type_name) expression 。type_name为新类型名称,expression为表达式。

(float) a;  //将变量 a 转换为 float 类型
(int)(x+y);  //把表达式 x+y 的结果转换为 int 整型
(float) 100;  //将数值 100(默认为int类型)转换为 float 类型

📝 再举一个例子:

#include<stdio.h>
int main()
{
  double forever = 520.1314;
  //强制将520.1314转成int类型,就需要舍去小数部分,即变为520
  int nowForMe = (int)forever;
  printf("其实我很想 %.4lf,但是现在我却只能做到 %d", forever, nowForMe);
  return 0;
}

8.3 类型转换只是临时性的

无论是自动类型转换还是强制类型转换,都只是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。

float a = 13.14;
/*
  这里进行了强制类型转换,首先由于()的优先级别高于 * ,因此先进行(int)a 得到该值为13
  然后 13 * 10 得到 130,所以 b 为 130
  但需要注意:虽然a在运算中转成了int类型,但实际上a依旧为 float 类型
*/
int b = (int)a * 10;

8.4 自动类型转换 VS 强制类型转换

在C语言中,有些类型既可以自动转换,也可以强制转换,例如 int 到 double,float 到 int 等;而有些类型只能强制转换,不能自动转换,例如以后将要学到的 void * 到 int *,int 到 char * 等。

可以自动转换的类型一定能够强制转换,但是,需要强制转换的类型不一定能够自动转换。现在我们学到的数据类型,既可以自动转换,又可以强制转换,以后我们还会学到一些只能强制转换而不能自动转换的类型。

可以自动进行的类型转换一般风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶多是数值失真。只能强制进行的类型转换一般风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会导致取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,一般会导致程序崩溃。

使用强制类型转换时,程序员自己要意识到潜在的风险。

🔖 小结

💬 最后再啰嗦一句:当格式控制符(占位符)数据类型不匹配时,编译器会给出警告,提示程序员可能会存在风险。编译器的警告是分等级的,不同程度的风险被划分成了不同的警告等级,而使用%d输出 short 和 long 类型的风险较低,如果你的编译器设置只对较高风险的操作发出警告,那么此处你就看不到警告信息。

💬 遇到任何疑惑请联系软件协会。

🏠 了解更多关注软协官网:https://www.csuftsap.cn/

💚 来自软件协会编辑,注册会员即可获取全部开源.md资源,请勿转载,归软件协会所有。

相关文章
|
1月前
|
存储 C语言
【c语言】数据类型和变量
本文介绍了C语言中的数据类型和变量。数据类型分为内置类型和自定义类型,内置类型包括字符型、整型、浮点型等,每种类型有不同的内存大小和取值范围。变量分为全局变量和局部变量,它们在内存中的存储位置也有所不同,分别位于静态区和栈区。通过示例代码和图解,详细阐述了这些概念及其应用。
43 1
|
1月前
|
存储 C语言
C语言指针与指针变量的区别指针
指针是C语言中的重要概念,用于存储内存地址。指针变量是一种特殊的变量,用于存放其他变量的内存地址,通过指针可以间接访问和修改该变量的值。指针与指针变量的主要区别在于:指针是一个泛指的概念,而指针变量是具体的实现形式。
|
1月前
|
存储 C语言
C语言:设置地址为 0x67a9 的整型变量的值为 0xaa66
在C语言中,可以通过指针操作来实现对特定地址的访问和赋值。要将地址为 0x67a9 的整型变量值设为 0xaa66,可以先定义一个指向该地址的指针,并通过该指针对该内存位置进行赋值操作。需要注意的是,直接操作内存地址具有一定风险,必须确保地址合法且可写。代码示例应考虑字节序及内存对齐问题。
|
1月前
|
存储 C语言
初识C语言:常量与变量中寻找数据类型
初识C语言:常量与变量中寻找数据类型
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
C语言
《C语言编程初学者指南》一2.6 理解常量
本节书摘来自华章出版社《C语言编程初学者指南》一书中的第2章,第2.6节,作者【美】Keith Davenport(达文波特) , M1ichael Vine(维恩),更多章节内容可以访问云栖社区“异步社区”公众号查看 2.6 理解常量 常量(constant)数据类型通常也叫做只读变量,在程序执行中,它们不会丢失其数据值。
1546 0
|
14天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
30 6
|
1月前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
40 10
|
28天前
|
存储 算法 程序员
C语言:库函数
C语言的库函数是预定义的函数,用于执行常见的编程任务,如输入输出、字符串处理、数学运算等。使用库函数可以简化编程工作,提高开发效率。C标准库提供了丰富的函数,满足各种需求。
|
1月前
|
机器学习/深度学习 C语言
【c语言】一篇文章搞懂函数递归
本文详细介绍了函数递归的概念、思想及其限制条件,并通过求阶乘、打印整数每一位和求斐波那契数等实例,展示了递归的应用。递归的核心在于将大问题分解为小问题,但需注意递归可能导致效率低下和栈溢出的问题。文章最后总结了递归的优缺点,提醒读者在实际编程中合理使用递归。
61 7
下一篇
无影云桌面