C 01_C语言数据类型和变量常量

一、数据类型(Data Type)

1.1 数据类型

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

也就是说,内存中的数据有多种解释方式,使用之前必须要确定;如 int a; 就表明,这份数据是整数,不能理解为像素、声音等其它数据。像 int 这样用于说明数据的关键字有一个专业的称呼,叫做 数据类型(Data Type)

数据类型 是用来说明数据的真实类型,它确定了数据的解释方式,让计算机和程序员不会产生歧义。

在C语言中,数据类型(Data Type) 分为基本类型构造类型指针类型空类型 四种。

  • 基础数据类型:包括 字符型(char)、整型(int)、浮点型float(实型)、 和 枚举类型;
  • 构造类型:包括数组、结构体类型 和 联合体类型。
  • 指针类型:是一个特殊的整型数据类型,它里面存储的数值被解释成为内存的一个地址。
  • 空类型:表示一种未知的类型,不能表示一个真实的变量。

C语言基础数据类型:

基本数据类型 说明
char 字符型,默认为有符号型,配合unsigned关键字,可以表示为无符号型。
int 整型,默认为有符号型,配合unsigned关键字,可以表示为无符号型。
float 单精度浮点型
double 双精度浮点型
enum 限定在一个有限的范围内的值
void 无类型,对函数返回的限定,对函数参数的限定。

char字符型、short短整型、int整型、long长整型,用以表示一个整数,默认为signed有符号型,配合unsigned关键字,表示为无符号型。

1.1.1 整型(int)

整数类型数据即整型数据,是没有小数部分的数值。整型数据可分为:基本型、短整型、长整型 和 无符号型 四种:

  • 基本型:以 int 表示;
  • 短整型:以 short int 表示;
  • 长整型:以 long int 表示;
  • 无符号型:存储单元中全部二进位用来存放数据本身,不包括符号;无符号型中又分为 无符号整型(unsigned int)、无符号短整型(unsigned short) 和 无符号长整型(unsigned long)。

Tips: 要注意的是,不同的计算机体系结构中这些类型所占比特位数有可能是不同的。

符号只有正负两种情况,用1位(Bit)就足以表示;C语言规定,把内存的最高位作为符号位,在符号位中,用 0 表示正数,用 1 表示负数。

short、int 和 long 类型默认都是带符号位的,符号位以外的内存才是数值位。

在数据类型前面加上 unsigned 关键字,这样,short、int、long 中就没有符号位了,所有的位都用来表示数值,正数的取值范围更大了。

虽然 int 与 unsigned int 所占的位数一样,但int的最高位用作了符号位,而unsigned int的最高位仍为数据位,所以它们的取值范围不同。

若要查看适合当前计算机的各数据类型的取值范围,可查看文件 “limits.h”(通常在编译器相关的目录下)。

在C语言中,整型可以使用十进制、八进制和十六进制表示。十进制是我们平常所用的数字表示法,八进制使用前缀 0 表示,十六进制使用前缀 0x 表示。

1
2
3
int a = 10; // 十进制表示
int b = 012; // 八进制整数的表示以数字0开头,012等价于十进制的10
int c = 0xA; // 十六进制整数的表示以0x开头, 0xA等价于十进制的10, {十六进制数前导字符0x,x前面是数字(0)}

在一个整型数据后面加一个字母L或l(小写),则认为是long int型量。如342L、0L、78L等,这往往用于函数调用中。如果函数的形参为long int型,则要求实参也为long int型,此时需要用342L作实参。

C语言并没有严格规定 short、int、long 的长度,只做了宽泛的限制:

  • short 至少占用 2 个字节。
  • int 建议为一个机器字长。32 位环境下机器字长为 4 字节,64 位环境下机器字长为 8 字节。
  • short 的长度不能大于 int,long 的长度不能小于 int。
  • 它们的长度(所占字节数)关系为:2 ≤ short ≤ int ≤ long

在 16 位环境下:short 的长度为 2 个字节,int 也为 2 个字节,long 为 4 个字节。16 位环境多用于单片机和低级嵌入式系统,在PC和服务器上已经见不到了。

对于 32 位的 Windows、Linux 和 Mac OS:short 的长度为 2 个字节,int 为 4 个字节,long 也为 4 个字节。PC和服务器上的 32 位系统占有率也在慢慢下降,嵌入式系统使用 32 位越来越多。

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

操作系统 short int long
Win64(64位 Windows) 2 4 4
类Unix系统(包括 Unix、Linux、Mac OS、BSD、Solaris 等) 2 4 8

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

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

1.1.2 字符型(char)

字符型(char)可以看作是整型的一种,它的标识符为 char,一般占用一个字节(8bit),它也分为有符号(singed)和 无符号(unsigned)两种,读者完全可以把它当成一个整型变量。当它用于存储字符常量时,实际上是将该字符的ASCⅡ码值(无符号整数)存储到内存单元中。字符变量用于存储字符常量。

1.1.3 实数型(浮点型)

实型变量又可分为单精度(float)、双精度(double)和长双精度(long double)3种。

在C语言程序设计中,实型数据有以下两种表达形式。

十进制数形式。由正负号、数字和小数点组成。如5.734、一0.273、0.8、一224等都是十进制数形式。 指数形式。如 546E+3 或 546E3 都代表 $546×10^3$。字母E(或e)之前必须有数字,E(或e)后面的指数必须为整数。 E8、4.6E+2.7、6e、e、9E7.5都是不合法的指数形式; 5.74E-7、-3E+6是合法的指数形式实型常量。

这些是最基本的数据类型,是C语言自带的,如果我们需要,还可以通过它们组成更加复杂的数据类型。

1.2 数据的长度(Length)

数据长度(Length) 一种数据类型占用的字节数,称为该数据类型的长度。占用的字节越多,能存储的数据就越多,对于数字来说,值就会更大,反之能存储的数据就有限。

多个数据在内存中是连续存储的,彼此之间没有明显的界限,如果不明确指明数据的长度,计算机就不知道何时存取结束。

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

C语言数字类型及长度

机器字长: 在同一时间中处理二进制数的位数叫字长。通常称处理字长为32位数据的CPU叫32位CPU,64位CPU就是在同一时间内处理字长为64位的二进制数据。字长=数据总线宽度。

Tips: 操作系统位数 = 其所依赖的指令集位数 <= CPU位数。

C语言标准并没有具体地规定各种整形变量在内存中到底应该分配几个字节的存储单元,所以不同机器上的编译系统都从自己的需要出发,规定各种整形变量在内存中应该分配多少个字节。

一般的原则是以一个机器字长(word)存放一个 int 型数据,而 long int 型数据的字节数应不小于 int 型、short int 型不长于 int 型。

数字类型及长度

Tips: 在现代操作系统中,int 一般占用 4 个字节的内存(总共 32 位)。使用 4 个字节保存较小的整数绰绰有余,会空闲出两三个字节来,这些字节就白白浪费掉了,不能再被其它数据使用。根据具体应用需求,当需要表示正负整数时可以考虑 shortchar 类型变量,只有正整数时可以考虑 unsined shortunsined char 来定义数据变量。

1.3 数据类型总结

数据是放在内存中的,在内存中存取数据要明确三件事情:数据存储在哪里、数据的长度以及数据的处理方式。

变量名不仅仅是为数据起了一个好记的名字,还告诉我们数据存储在哪里,使用数据时,只要提供变量名即可;而数据类型则指明了数据的长度和处理方式。所以诸如int n;、char c;、float money;这样的形式就确定了数据在内存中的所有要素。

C语言提供的多种数据类型让程序更加灵活和高效,同时也增加了学习成本。而有些编程语言,例如PHP、JavaScript等,在定义变量时不需要指明数据类型,编译器会根据赋值情况自动推演出数据类型,更加智能。

除了C语言,Java、C++、C#等在定义变量时也必须指明数据类型,这样的编程语言称为强类型语言。而PHP、JavaScript等在定义变量时不必指明数据类型,编译系统会自动推演,这样的编程语言称为弱类型语言。

强类型语言一旦确定了数据类型,就不能再赋给其它类型的数据,除非对数据类型进行转换。弱类型语言没有这种限制,一个变量,可以先赋给一个整数,然后再赋给一个字符串。

最后需要说明的是:数据类型只在定义变量时指明,而且必须指明;使用变量时无需再指明,因为此时的数据类型已经确定了。

二、 变量(Variable)

2.1 变量的介绍

数据在内存中的存储:

  • 计算机要处理的数据(如数字、文字、符号、图形、音频、视频等)都是以二进制的形式存放在内存中的;
  • 我们将8个比特(Bit)称为一个字节(Byte),并将字节作为最小的可操作单元。

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

变动的定义和赋值

现实生活中我们会找一个小箱子来存放物品,一来显得不那么凌乱,二来方便以后找到。计算机也是这个道理,我们需要先在内存中找一块区域,规定用它来存放整数,并起一个好记的名字,方便以后查找。这块区域就是“小箱子”,我们可以把整数放进去了。 C语言中这样在内存中找一块区域:

1
int a;  // int又是一个新单词,它是 Integer 的简写,意思是整数。

这个语句的意思是:在内存中找一块区域,命名为 a,用它来存放整数。

C语言中这样向内存中放整数:

1
2
int a;      // 在内存中找(定义)一块区域,命名为 a,用它来存放整数。
a = 123;    // 把 123 放到了一块叫做 a 的内存中

= 是一个新符号,它在数学中叫“等于号”,例如 1+2=3,但在C语言中,这个过程叫做赋值(Assign),赋值是指把数据放到内存的过程。

Tips: 赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。

a 中的整数不是一成不变的,只要需要,随时可以更改,更改的方式就是再次赋值,例如:

1
2
3
int a = 123;  // 把 123 放到了一块叫做 a 的内存中
a = 1000;
a = 9999;

第二次赋值,会把第一次的数据覆盖(擦除)掉,也就是说,a 中最后的值是9999,123、1000 已经不存在了,再也找不回来了。

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

int a; 创造了一个变量 a,我们把这个过程叫做 变量定义 a=123; 把 123 交给了变量 a,我们把这个过程叫做给变量赋值;又因为是第一次赋值,也称变量的初始化 或者 赋初值

  • 可以先定义变量,再初始化
  • 也可以在定义的同时进行初始化

这两种方式是等价的。

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

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

1
2
3
int a, b, c;
float m = 10.9, n = 20.56;
char p, q = '@';

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

2.2 变量的作用域和存储方式

作用域(scope) 是程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的而限定这个名字的可用性的代码范围就是这个名字的作用域。

在C语言中每个变量都有两种基本属性:数据类型数据的存储类别数据类型 有字符型(char)、整型(int)、浮点型(float)等等。存储类别 是指数据在内存中的存储方式,C语言中的存储类别共有4种:自动的(auto)静态的(static)寄存器的(register)外部的(extern)。根据变量的存储类别,可以知道变量的作用域和生存期。

  • 自动变量:关键字为auto,但实际上“auto”通常都可以省略,函数中的局部变量如果不用关键字static来定义则都默认为自动类型(auto)的变量,在调用函数时系统会将这些变量分配到内存的动态存储区中,函数调用结束后这些存储空间将被释放。平常使用的函数中未用static定义的局部变量都是auto类型。
  • 静态变量: 关键字为static。静态变量包括全局变量和静态局部变量。
    • 静态全局变量:在变量申请时自动存储到内存的静态存储区,即自动申请为静态变量。该变量的作用域被限定在本文件,即用static声明的全局变量仅在当前文件可见,不能被本文件以外的文件引用。
    • 静态局部变量:若希望函数中局部变量的值在函数调用结束后不消失,则需要用关键字static将该局部变量声明为静态局部变量,这些变量也被存储在内存的静态存储区。加上关键字static修饰后,原来的局部变量就类似于全局变量,它的值在下次调用该函数之前不会发生改变;与全局变量不同的是,全局变量的值可以在程序中的任何部分进行修改,而静态局部变量在程序中的其它部分是不可见的,其作用域为声明该变量的代码块(函数 或 函数内的代码块)。

Tips: 静态局部变量的几个要点

  • 静态局部变量存储在内存的静态存储区。
  • 对静态局部变量赋初值是在编译时进行的,即仅赋值一次。如果定义静态局部变量时不赋初值,则编译器将自动对其赋初值0(对数值型变量)或空字符(对字符型变量)。
  • 静态局部变量的值在函数调用结束后不发生改变,但在程序中的其它部分是不可见的。
  • 寄存器变量:关键字register。变量的值一般是存储在计算机的内存中的,但对于一些频繁调用的变量,为了减少变量的调用时间可将其存在CPU的寄存器中,定义这样的变量需要使用关键字register。需要说明的是,现在优化的编译器能够识别使用频繁的变量,从而自动将其放入CPU的寄存器中,因此需要程序员使用register关键字来定义寄存器变量的场合较少。

  • 外部变量:外部变量是在函数外部定义的全局变量。关键字“extern”主要用来扩展外部变量的作用域。通常来说,外部变量的生存周期为程序的整个运行过程,它的作用域从定义该变量处到本程序文件的末尾,在此范围内该变量都是可见的。但是如果需要扩展外部变量的作用范围,就需要用到关键字extern。有以下两种扩展外部变量的情况:

    • 在一个文件内扩展外部变量的作用域。外部变量的作用域从定义该变量处到本程序文件的末尾,如果需要在定义之前使用该外部变量,应在使用之前用extern对该变量进行外部变量声明。
    • 将外部变量的作用域扩展到其它文件。如果在本文件中定义的变量希望能用于其它文件,则可以在需要用到该变量的文件开头加入外部变量声明。

Tips: C语言默认全局变量均为 extern类型,故定义其它文件可以使用的全局变量时可(一般)省略 extern 声明。在使用到该这类全局变量的文件中需要使用 extern 声明

三、、常量(Const)

3.1 常量(Const)简介

有时候我们希望定义这样一种“变量”,它的值不能被改变,在整个作用域中都保持固定, 这样的"变量"称做 常量(const)

在C语言中定义和使用 常量(const) 的方法有四种:

  • 字面常量

  • const 修饰的常变量

  • #define 定义的标识符常量 #define 宏定义, 该方式定义的常量在程序预编译阶段宏展开时进行字符替换,一般使用全大写标识符定义和使用, 详细介绍见: C-03_C语言预处理命令详解

  • 枚举常量

3.2 字面常量

字面常量 是指在代码中直接写出来的数值、字符、字符串等等,如下所示:

1
2
3
4
5
6
7
8
int main()
{
	3; 
	3.15;
	'a';
	"abcd";
	return 0;
}

3.3 const 修饰的常变量

使用 const 修饰定义的“变量”[称做 常量(const)],用const关键字修饰了变量,使这个变量具有了常属性。常属性就是不能被更改的属性,从语法层面直接限制了这个变量,常量的作用域和生命周期与变量相同,建议将常量名的首字母大写,以提醒程序员这是个常量。

3.4 #define 定义的标识符常量

1
#define MAX 10000

这里 #define 定义了一个 MAX 常量

  • 符号名是MAX 本质上代表的数字是10000
  • 这里代表的意思是10000本来是没有什么意义的,
  • 但是我们需要一个最大值MAX,我们把10000定义成我们的最大值
  • 以后我们的最大值MAX代表的就是10000

3.5 枚举常量

枚举 一一列举

四、字符串(string)

4.1 字符串数组

用来存放字符的数组称为 字符数组, 字符数组实际上是一系列字符的集合,也就是 字符串(String)。 在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。 字符数组 一般存储在全局数据区、堆区或栈区,具有读取和写入的权限。

在C语言中,字符串总是以'\0'作为结尾,所以'\0'也被称为字符串结束标志,或者字符串结束符。

Tips: '\0'是 ASCII 码表中的第 0 个字符,英文称为 NUL,中文称为“空字符”。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言中唯一的作用就是作为字符串结束标志。

C语言在处理字符串时,会从前往后逐个扫描字符,一旦遇到'\0'就认为到达了字符串的末尾,就结束处理。'\0'至关重要,没有'\0'就意味着永远也到达不了字符串的结尾。

Tips: C语言中使用 strlen() 计算字符串长度, 计算的字符串长度不包含字符串结束符'\0'

在C语言中,使用 “abc” 包围的字符串会自动在末尾添加'\0'(字符串结束符),即使是空字符串 sizeof("")/sizeof(char) == 1 , strlen("") == 0。 这意味着,字符占用的内存空间(使用 sizeof 计算)至少要比字符串的长度(使用 strlen 获取)大 1。

C语言规定,可以逐个字符地给字符数组赋值定义字符串,也可以将字符串直接赋值给字符数组,为了方便,也可以不指定数组长度, 如下示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 逐个字符给数组赋值定义字符串方式(不会自动添加'\0', 需要定义时明确指定)
char str[20]={'C', '  ', 'P', 'r', 'o', 'g', 'r', 'a', 'm', '\0'};  // 给部分数组元素赋值, 字符串的长度为9(字符串结束符不计算在字符串长度内),数组的长度为 20(定义数组时明确指定了数组长度)。
char str0[]={'C', ' ', 'P', 'r', 'o', 'g', 'r', 'a', 'm', '\0'};  // 对全体元素赋值时可以省去长度,字符串的长度为9(字符串结束符不计算在字符串长度内),数组的长度为 10(包括字符串结束符占用的内存空间)。

// 指定数组长度, 数组长度必须大于等于字符串中字符数量 + 1(自动添加字符串结束符'\0')
char str1[30] = {"C Language"};
char str2[30] = "C Language";  // 这种形式更加简洁,实际开发中常用

// 不指定数组长度, 自动计算数组长度 == strlen("C Language") + 1 == sizeof("C Language")/sizeof(char), (自动添加字符串结束符'\0')
char str3[] = {"C Language"};
char str4[] = "C Language";  // 这种形式更加简洁,实际开发中常用

// 字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了
str1 = "abc123";  // 错误写法, 

// 正确写法
str1[0] = 'a';
str1[1] = 'b';
str1[2] = 'c';

Tips:

  • 字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了
  • 逐个字符给数组赋值定义字符串方式并不会自动添加’\0’

需要特别注意的是:

  • 逐个字符给数组赋值定义字符串时要小心缺失字符串结束符
  • 修改已经有的字符串数组时要小心误修改了字符串结束符导致字符串缺失结束符 如下示例:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 定义字符串时缺失(不会自动添加, 也未明确指定)字符串结束符
char str0[10]={'a', 'b', 'c'};      // 定义字符串时缺失字符串结束符,字符串的实际长度为3(使用 strlen 不能准确获取字符串长度,因为没有字符串结束符),数组的长度为 10(定义数组时明确指定了数组长度)
char str1[3]={'a', 'b', 'c'};       // 定义字符串时缺失字符串结束符,字符串的实际长度为3(使用 strlen 不能准确获取字符串长度,因为没有字符串结束符),数组的长度为 3(定义数组时明确指定了数组长度)
char str2[]={'a', 'b', 'c'};        // 定义字符串时缺失字符串结束符,字符串的长度为3(使用 strlen 不能准确获取字符串长度,因为没有字符串结束符),数组的长度为 3(定义没有包括字符串结束符占用的内存空间)

// 定义字符串时明确指定 或自动添加了字符串结束符
char str3[10]={'a', 'b', 'c', '\0'}; // 字符串的实际长度为3(字符串结束符不计算在字符串长度内),数组的长度为 10(定义数组时明确指定了数组长度)
char str4[]={'a', 'b', 'c', '\0'};  // 字符串的长度为3(字符串结束符不计算在字符串长度内),数组的长度为 4(包括字符串结束符占用的内存空间)
char str5[10] = {"abc"};            // 字符串的长度为3(字符串结束符不计算在字符串长度内),数组的长度为 10(定义数组时明确指定了数组长度)
char str6[] = {"abc"};              // 字符串的长度为3(字符串结束符不计算在字符串长度内),数组的长度为 4(包括字符串结束符占用的内存空间)
char str7[10] = "abc";              // 字符串的长度为3(字符串结束符不计算在字符串长度内),数组的长度为 4(包括字符串结束符占用的内存空间)
char str8[] = "abc";              // 字符串的长度为3(字符串结束符不计算在字符串长度内),数组的长度为 4(包括字符串结束符占用的内存空间)

// 以下修改已经有的字符串数组时误修改了字符串结束符导致字符串缺失结束符
str3[3] = 'x';
str4[3] = 'x';
str5[3] = 'x';
str6[3] = 'x';
str7[3] = 'x';
str8[3] = 'x';

4.2 字符串常量

除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个 指针 指向字符串,例如:

1
2
3
4
5
6
// 1. 定义字符串指针变量并初始化
char *str = "C Language";   // 定义字符串指针变量str 并初始化指向字符串常量

// 先定义字符串指针变量,在赋值
char *strs;
strs = "C Language";

字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0 个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *。

C语言中,这种方式定义的字符串存储在常量区,只有读取权限,没有写入权限。一旦被定义后就只能读取字符不能修改,任何对字符的赋值都是错误的。称为字符串常量,只能读取不能写入。

Tips:

  • 指针变量本身可以修改(指向其它字符串常量或字符串数组)
  • 指针指向的 字符串常量 不能修改

4.3 字符串总结

C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。

Licensed under CC BY-NC-SA 4.0
最后更新于 2023-03-07 22:16 CST