C语言变量与类型

 


C语言变量与类型

  • 计算机要处理的数据(诸如数字、文字、符号、图形、音频、视频等)是以二进制的形式存放在内存中的;
  • 我们将 8 个比特(Bit)称为一个字节(Byte),并将字节作为最小的可操作单元。
  • C 是一门静态类型语言。
  • 这意味着任何变量都有一个相关联的类型,并且该类型在编译时是可知的。
  • 这与 Python、JavaScript、PHP 和其它解释型语言中使用变量的方式大有不同。
  • 当在 C 中创建变量时,必须在声明中给出该变量的类型。

在这个示例中,我们初始化一个 int 类型的变量 age,C语言就是这样在内存中找一块区域的。

int age;

int又是一个新单词,它是 Integer 的简写,意思是整数。age是我们给这块区域起的名字;当然也可以叫其他名字,例如 abc、mn123、student_number 等。变量名可以包含任意大写或小写字母,也可以包含数字和下划线,但是不能以数字开头。AGE 和 Age10 都是有效的变量名,但 1age 就不是了。注意 int 和 age 之间是有空格的,它们是两个词。也注意最后的分号,int age表达了完整的意思,是一个语句,要用分号来结束。

你还可以在声明中初始化变量,给出初始值即可,C语言中这样向内存中放整数:

int age = 37;

这个语句的意思是:在内存中找一块区域,命名为 age,用它来存放整数。变量一旦声明,你就可以在程序代码中使用它了。

在任何时候都可以使用 = 改变它的值,例如 age = 100;(提供的新值的类型与原值相同)。=是一个新符号,它在数学中叫“等于号”,例如 1+2=3,但在C语言中,这个过程叫做赋值(Assign)。赋值是指把数据放到内存的过程。

在这种情况下:

#include <stdio.h>
int main(void) {
    int age = 0;
    age = 37.2;
    printf("%u", age);
}

编译器会在编译时发出警告,然后将小数转为整数。第二次赋值,会把第一次的数据覆盖(擦除)掉,也就是说,age中最后的值是 37,之前的0已经不存在了。

因为 a 的值可以改变,所以我们给它起了一个形象的名字,叫做变量(Variable)。

 

int age;创造了一个变量 age,我们把这个过程叫做变量定义。age=99;把 99 交给了变量 age,我们把这个过程叫做给变量赋值;又因为是第一次赋值,也称变量的初始化,或者赋初值。
你可以先定义变量,再初始化,例如:

int age;
age=18

也可以在定义的同时进行初始化,例如:

int age=18;

这两种方式是等价的。

数据类型(Data Type)

数据是放在内存中的,变量是给这块内存起的名字,有了变量就可以找到并使用这份数据。

我们知道,诸如数字、文字、符号、图形、音频、视频等数据都是以二进制形式存储在内存中的,它们并没有本质上的区别,那么,00010000 该理解为数字 16 呢,还是图像中某个像素的颜色呢,还是要发出某个声音呢?如果没有特别指明,我们并不知道。

也就是说,内存中的数据有多种解释方式,使用之前必须要确定;上面的int age;就表明,这份数据是整数,不能理解为像素、声音等。int 有一个专业的称呼,叫做数据类型(Data Type)。
顾名思义,数据类型用来说明数据的类型,确定了数据的解释方式,让计算机和程序员不会产生歧义。在C语言中,有多种数据类型,例如:

数据类型说明数据类型说明
_Bool 布尔型(C99) long long 超长整形(C99)
char 字符型 float 单精度浮点型
short 短整型 double 双精度浮点型
int 整型 long double 长双精度浮点型
long 长整型 void 无类型

C 的内置数据类型有 intcharshortlongfloatdoublelong double

连续定义多个变量

为了让程序的书写更加简洁,C语言支持多个变量的连续定义,例如:

int a, b, c;
float m = 0.27, n = 93.25;
char p, q = '#';

连续定义的多个变量以逗号,分隔,并且要拥有相同的数据类型;变量可以初始化,也可以不初始化。

数据的长度(Length)

所谓数据长度(Length),是指数据占用多少个字节。占用的字节越多,能存储的数据就越多,对于数字来说,值就会更大,反之能存储的数据就有限。

多个数据在内存中是连续存储的,彼此之间没有明显的界限,如果不明确指明数据的长度,计算机就不知道何时存取结束。例如我们保存了一个整数 520,它占用 4 个字节的内存,而读取时却认为它占用 3 个字节或 5 个字节,这显然是不正确的。

所以,在定义变量时还要指明数据的长度。而这恰恰是数据类型的另外一个作用。数据类型除了指明数据的解释方式,还指明了数据的长度。因为在C语言中,每一种数据类型所占用的字节数都是固定的,知道了数据类型,也就知道了数据的长度。

在 32 位环境中,各种数据类型的长度一般如下:

数据类型说明长度(字节)
_Bool 布尔型(C99) 1
char 字符型 1
short 短整型 2
int 整型 4
long 长整型 4
long long 超长整形 8
float 单精度浮点型 4
double 双精度浮点型 8
long double 长双精度浮点型 8


在现代操作系统中,int 一般占用 4 个字节(Byte)的内存,共计 32 位(Bit)。如果不考虑正负数,当所有的位都为 1 时它的值最大,为 232-1 = 4,294,967,295 ≈ 43亿,这是一个很大的数,实际开发中很少用到,而诸如 18、520、1024 等较小的数使用频率反而较高。

使用 4 个字节保存较小的整数绰绰有余,会空闲出两三个字节来,这些字节就白白浪费掉了,不能再被其他数据使用。现在个人电脑的内存都比较大了,配置低的也有 2G,浪费一些内存不会带来明显的损失;而在C语言被发明的早期,或者在低端单片机中,内存都是非常稀缺的资源,所有的程序都在尽力节省内存。

反过来说,43 亿虽然已经很大,但要表示全球人口数量还是不够,必须要让整数占用更多的内存,才能表示更大的值,比如占用 6 个字节或者 8 个字节。

让整数占用更少的内存可以在 int 前边加 short,让整数占用更多的内存可以在 int 前边加 long,还不够的话可以在 int 前面加两个 long,例如:

short int a = 18;
short int b, c = 520;
long int m = 102023;
long int n, p = 562131;
long long int x = 12233720;
long long int y, z = 92949685;

这样 a、b、c 各自只占用 2 个字节的内存,m、n、p 可能占用 8 个字节,x、y、z 各自占用 8 个字节。
也可以将 int 省略,只写 short、long 和 long long,如下所示:

short a = 18;
short b, c = 520;
long m = 102023;
long n, p = 562131;
long long x = 12233720;
long long y, z = 92949685;

这样的写法更加简洁,实际开发中常用。

int 是基本的整数类型,short、long 和 long long 是在 int 的基础上进行的扩展,short 可以节省内存,long 和 long long 可以容纳更大的值。

short、int、long 是C语言中常见的整数类型,long long 在某些场景中也会用到。其中,int 称为整型,short 称为短整型,long 称为长整型,long long 称为超长整形。

整型的长度

细心的读者可能会发现,上面我们在描述 short、int、long、long long 类型的长度时,只对 short 和 long long 使用肯定的说法,而对 int、long 使用了“一般/可能”等不确定的说法。这种描述的言外之意是,short 和 long long 的长度是确定的,分别是 2 和 8 个字节,而 int、long 的长度无法确定,在不同的环境下有不同的表现。

一种数据类型占用的字节数,称为该数据类型的长度。例如,short 占用 2 个字节的内存,那么它的长度就是 2。

实际情况也确实如此,C语言并没有严格规定 short、int、long、long long 的长度,只做了宽泛的限制:

  • short 至少占用 2 个字节。
  • int 建议为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。
  • short 的长度不能大于 int,long 的长度不能小于 int,long long 不能小于 long。


总结起来,它们的长度(所占字节数)关系为:

2 ≤ short ≤ int ≤ long ≤ long long

这就意味着,short 并不一定真的“短”,long 也并不一定真的“长”,它们有可能和 int 占用相同的字节数。同样,long long 也不一定真的比 long 长,它们占用的字节数可能相同。

在 16 位环境下,short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节,long long 为 8 个字节。在单片机/嵌入式领域,16 位环境很少使用了,一般使用 8/32/64 位环境;在 PC 和服务器领域,16 位环境早就已经消失了。

对于 32 位的 Windows、Linux 和 macos,short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节,long long 为 8 个字节。PC 和服务器上的 32 位操作系统占有率越来越低,基本都被 64 位替代了;而单片机/嵌入式领域,使用 32 位环境越来越多了,8 位环境越来越少了。

在 64 位环境下,不同的操作系统会有不同的结果,如下所示:

操作系统shortintlonglong long
Win64(64位 Windows) 2 4 4 8
类Unix系统(包括 Unix、Linux、macOS、BSD、Solaris 等) 2 4 8 8


目前我们使用较多的 PC 系统为 Win 7/8/10/11、macOS、Linux,在这些系统中,short、int 和 long long 的长度都是固定的,分别为 2、4 和 8,大家可以放心使用,只有 long 的长度在 Win64 和类 Unix 系统下会有所不同,使用时要注意移植性。

不同整型的输出

使用不同的格式控制符可以输出不同类型的整数,它们分别是:

  • %hd用来输出 short int 类型,hd 是 short decimal 的简写;
  • %d用来输出 int 类型,d 是 decimal 的简写;
  • %ld用来输出 long int 类型,ld 是 long decimal 的简写;
  • %lld用来输出 long long int 类型,lld 是 long long decimal 的简写;


下面的例子演示了不同整型的输出:

复制代码
#include <stdio.h>
int main()
{
short a = 18;
int b = 520;
long c = 1024;
long long d = 102023;
printf("a=%hd, b=%d, c=%ld, d=%lld\n", a, b, c, d);
return 0;
}
复制代码

运行结果:
a=18, b=520, c=1024, d=102023

在编写代码的过程中,我建议将格式控制符和数据类型严格对应起来,养成良好的编程习惯。当然,如果你不严格对应,一般也不会导致错误,例如,很多初学者都使用%d输出所有的整数类型,请看下面的例子:

复制代码
#include <stdio.h>
int main()
{
short a = 18;
int b = 520;
long c = 1024;
long long d = 102023;
printf("a=%d, b=%d, c=%d, d=%d\n", a, b, c, d);
return 0;
}
复制代码

运行结果仍然是:
a=18, b=520, c=1024, d=102023

当使用%d输出 short,或者使用%ld输出 short、int,又或者使用%lld输出 short、int 和 long 时,不管值有多大,都不会发生错误,因为格式控制符足够容纳这些值。

当使用%hd输出 int、long、long long,或者使用%d输出 long、long long,又或者使用%ld输出 long long 时,如果要输出的值比较小(就像上面的情况),一般也不会发生错误,如果要输出的值比较大,就很有可能发生错误,例如:

复制代码
#include <stdio.h>
int main()
{
int m = 306587;
long n = 28166459852;
printf("m=%hd, n=%hd\n", m, n);
printf("n=%d\n", n);
return 0;
}
复制代码

在 64 位 Linux 和 macOS 下(long 的长度为 8)的运行结果为:
m=-21093, n=4556
n=-1898311220

输出结果完全是错误的,这是因为%hd容纳不下 m 和 n 的值,%d也容纳不下 n 的值。感兴趣的话,还可以在 Windows 环境中测试用%ld输出较大的 long long 类型整数(比如 28166459852),会发现输出结果也是错误的。

读者需要注意,当格式控制符和数据类型不匹配时,编译器会给出警告,提示程序员可能会存在风险。

编译器的警告是分等级的,不同程度的风险被划分成了不同的警告等级,而使用%d输出 short、long 和 long long 类型的风险较低,如果你的编译器设置只对较高风险的操作发出警告,那么此处你就看不到警告信息。

不同整形的后缀

读到这里,大家已经学完了 short、int、long 和 long long 四种整数类型,假设程序中有一个整数 100,它是什么类型的呢?

实际开发中用到的整数,它们的值通常不会很大,类型默认为 int。如果整数的值很大,需要占用比 int 更多的内存,它的类型可能是 long、long long 等(还可能是无符号整形,后续会讲)。

举个简单的例子:

  1. long a = 100;
  2. int b = 294;

单纯看 100 和 294 这两个整数,它们的类型都是 int。分析这两行代码:

  • 第一行:变量 a 的类型是 long,整数 100 的类型是 int,它们的类型不同,编译器会先将 100 的类型转换成 long,再将它赋值给 a;
  • 第二行:变量 b 的类型是 int,整数 294 的类型是 int,它们的类型相同,编译器直接将 294 赋值给变量 b。

关于数据类型的转换,这里先简单了解一下.

对于数值不是很大的整数,我们也可以手动指定它们的类型为 long 和 long long,具体写法是:

  • 整数后面紧跟 l(小写的 L)或者 L,表明它的类型是 long;
  • 整数后面紧跟 ll(小写的 LL)或者 LL,表明它的类型是 long long。


举个简单的例子:

int a = 10;
long b = 100L;
long long c = 1000LL;
short d = 32L;

其中,10 的类型是 int,100 和 32 的类型是 long,1000 的类型是 long long。我们习惯把 L、LL 这样的类型标识叫做整数的后缀。

再次强调,一个整数赋值给某个变量时,它们的类型不一定相同,比如将 100L 赋值给 b,它们的类型是相同的;再比如将 32L 赋值给 d,它们的类型是不同的,编译器会先将 32 转换为 short 类型,然后再赋值给 d。

对于初学者,很少会用到数字的后缀,加不加往往没有什么区别,也不影响实际编程。但是既然系统地学习 C语言,这个知识点还是要掌握的,否则哪天看到别人的代码这样写,你却不明白怎么回事,那就尴尬了。

整数

C 给我们提供了下列定义整数的类型:

  • char
  • int
  • short
  • long

通常,你很可能会使用 int 保存整数。但是在某些情况下,你或许想在其它三个选项中选取合适的类型。

char 类型通常被用来保存 ASCII 表中的字母,但是它也可以用来保存 -128 到 127 之间的小整数。它占据至少一个字节。

int 占据至少两个字节。short 占据至少两个字节。long 占据至少四个字节。

如你所见,我们并不保证不同环境下的值相同。我们只有一个指示。问题在于每种数据类型中所存储的具体值是由实现和系统架构决定的。

我们保证 short 不会比 int 长。并且我们还保证 long 不会比 int 短。

ANSI C 规范标准确定了每种类型的最小值,多亏了它,我们至少可以知道使用某个类型时可以期待的最小值。

如果你正在 Arduino 上用 C 编程,不同的板子上的限制会有所不同。

在 Arduino Uno 开发板上,int 占两个字节,范围从 -32,768 到 32,767。在 Arduino MKR 1010 上,int 占四个字节,范围从 -2,147,483,648 到 2,147,483,647。差异还真不小。

在所有的 Arduino 开发板上,short 都占两个字节,范围从 -32,768 到 32,767long 占四个字节,范围从 -2,147,483,648 到 2,147,483,647

无符号整数

对于以上所有的数据类型,我们都可以在其前面追加一个 unsigned。这样一来,值的范围就不再从负数开始,而是从 0 开始。这在很多情况下是很有用的。

  • unsigned char 的范围从 0 开始,至少到 255
  • unsigned int 的范围从 0 开始,至少到 65,535
  • unsigned short 的范围从 0 开始,至少到 65,535
  • unsigned long 的范围从 0 开始,至少到 4,294,967,295

溢出的问题

鉴于所有这些限制,可能会出现一个问题:我们如何确保数字不超过限制?如果超过了限制会怎样?

如果你有一个值为 255 的 unsigned int,自增返回的值为 256,这在意料之中。如果你有一个值为 255 的 unsigned char,你得到的结果就是 0。它重置为了初始值。

如果你有一个值为 255 的 unsigned char,给它加上 10 会得到数字 9

#include <stdio.h>
int main(void) {
  unsigned char j = 255;
  j = j + 10;
  printf("%u", j); /* 9 */
}

If you don't have a signed value, the behavior is undefined.
原文这里可能是 typo,从代码来看,这里描述的是有符号整数的溢出行为。

如果你的值是有符号的,程序的行为则是未知的。程序基本上会给你一个很大的值,这个值可能变化,就像这样:

include <stdio.h>
int main(void) {
  char j = 127;
  j = j + 10;
  printf("%u", j); /* 4294967177 */
}

换句话说,C 并不会在你超出类型的限制时保护你。对于这种情况,你需要自己当心。

声明错误类型时的警告

如果你声明变量并用错误的值进行初始化,gcc 编译器(你可能正在使用这个编译器)应该会发出警告:

复制代码
#include <stdio.h>

int main(void) {
  char j = 1000;
}
hello.c:4:11: warning: implicit conversion 
  from 'int' to
      'char' changes value from 1000 to -24
      [-Wconstant-conversion]
        char j = 1000;
             ~   ^~
1 warning generated.
复制代码

如果你直接赋值,也会有警告:

#include <stdio.h>

int main(void) {
  char j;
  j = 1000;
}

但是对值进行增加操作(例如,使用 +=)就不会有警告:

#include <stdio.h>

int main(void) {
  char j = 0;
  j += 1000;
}
浮点数

浮点类型可以表示的数值范围比整数大得多,还可以表示整数无法表示的分数。

使用浮点数时,我们将数表示成小数乘以 10 的幂。

你可能见过浮点数被写成

  • 1.29e-3
  • -2.3e+5

和其它的一些看起来很奇怪的形式。

下面的几种类型:

  • float
  • double
  • long double

是用来表示带有小数点的数字(浮点类型)的。这几种类型都可以表示正数和负数。

任何 C 的实现都必须满足的最小要求是 float 可以表示范围在 10^-37 到 10^+37 之间的数,这通常用 32 位比特实现。 double 可以表示一组更大范围的数,long double 可以保存的数还要更多。

与整数一样,浮点数的确切值取决于具体实现。

在现代的 Mac 上,float 用 32 位表示,精度为 24 个有效位,剩余 8 位被用来编码指数部分。

double 用 64 位表示,精度为 53 个有效位,剩余 11 为用于编码指数部分。

long double 类型用 80 位表示,精度为 64 位有效位,剩余 15 位被用来编码指数部分。

你如何能在自己的计算机上确定这些类型的大小呢?你可以写一个程序来干这事儿:

复制代码
#include <stdio.h>

int main(void) {
  printf("char size: %lu bytes\n", sizeof(char));
  printf("int size: %lu bytes\n", sizeof(int));
  printf("short size: %lu bytes\n", sizeof(short));
  printf("long size: %lu bytes\n", sizeof(long));
  printf("float size: %lu bytes\n", sizeof(float));
  printf("double size: %lu bytes\n", sizeof(double));
  printf("long double size: %lu bytes\n", sizeof(long double));
}
复制代码

在我的系统上(一台现代 Mac),输出如下:

char size: 1 bytes
int size: 4 bytes
short size: 2 bytes
long size: 8 bytes
float size: 4 bytes
double size: 8 bytes
long double size: 16 bytes

常量

咱们现在来谈谈常量。

常量的声明与变量类似,不同之处在于常量声明的前面带有 const 关键字,并且你总是需要给常量指定一个值。

就像这样:

const int age = 37;

这在 C 中是完全有效的,尽管通常情况下将常量声明为大写,就像这样:

const int AGE = 37;

虽然这只是一个惯例,但是在你阅读或编写 C 程序时,他能给你提供巨大的帮助,因为它提高了可读性。大写的名字意味着常量,小写的名字意味着变量。

常量的命名规则与变量相同:可以包含任意大小写字母、数字和下划线,但是不能以数字开头。AGE 和 Age10 都是有效的变量名,而 1AGE 就不是了。

另一种定义常量的方式是使用这种语法:

#define AGE 37

在这种情况下,你不需要添加类型,也不需要使用等于符号 =,并且可以省略末尾的分号。

C 编译器将会在编译时从声明的值推断出相应的类型。

posted @   luckylan  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示

目录导航