第二讲:C语言数据类型和变量

📅 2026/7/24 22:16:31 👁️ 阅读次数 📝 编程学习
第二讲:C语言数据类型和变量

目录

一、数据类型介绍

1. 什么是数据类型

2. 常见的数据类型分类

2.1 内置数据类型

2.2 自定义数据类型

二、变量

1. 变量的创建与命名规则

1.1 变量的创建(声明与初始化)

1.2 变量的命名规则

2. 变量的作用域:全局变量与局部变量

2.1 全局变量

2.2 局部变量

2.3全局变量和局部变量在内存中存储的位置

2.4 对比与注意事项

三、算数操作符

1. 加法与减法操作符:+ 和 -

2. 乘法与除法操作符:* 和 /

3. 取模操作符:%

4. 算术操作符的优先级与结合性

四、 赋值操作符:=和复合赋值

1. 连续赋值

2. 复合赋值

五、单目操作符:++ 、-- 、+ 、-

1. ++和--

2. + 和 -

六、强制类型转换

七、scanf 和 printf 的介绍

1. printf 函数:格式化输出

1.1 基本用法

1.2 占位符

1.3 占位符举例

1.4 输出格式

1.4.1 限定宽度

1.4.2 总是显示正负号

1.4.3 限定小数位数

1.4.4 输出部分字符串

2. scanf 函数:格式化输入

2.1 基本用法

2.2 scanf 的返回值(printf:返回成功输出的字符数,失败时返回负值。)

2.3 占位符(与 printf 类似)


一、数据类型介绍

1. 什么是数据类型

在计算机科学中,数据类型(Data Type)是一个基础且核心的概念。它定义了数据的性质、取值范围、存储方式以及可以对其执行的操作。简单来说,数据类型决定了计算机如何理解和处理一段数据。

例如,数字42和文本"42"对人类来说可能含义相似,但对计算机而言,前者是整数,可以进行加减乘除运算;后者是字符串,可以进行拼接、查找等操作。明确数据类型是编写正确、高效程序的第一步。

2. 常见的数据类型分类

数据类型通常可以分为两大类:内置类型和自定义类型

2.1 内置数据类型

内置数据类型是不可再分的最小数据单元。它们通常直接存储在栈内存中,操作速度快。

  • 整型:用于表示整数,如intshortlong。在 C/C++ 等语言中,整型还可以分为有符号(signed)无符号(unsigned)两种。有符号整型可以表示正数、负数和零,而无符号整型只能表示非负数(零和正数),因此其正数表示范围更大。
  • 浮点型:用于表示带小数点的数字,如floatdouble
  • 字符型:用于表示单个字符,如char。字符类型存储的时候是存储ASCII值,ASCII值是整数,所以字符类型是属于整型家族的。
  • 布尔型:用于表示逻辑真或假,只有两个值:truefalse

2.2 自定义数据类型

自定义数据类型是由基本数据类型或其他复合类型组合构造而成的。它们的数据通常存储在堆内存中,变量本身存储的是对数据的引用(地址)。

  • 数组:用于存储固定大小的同类型元素集合。
  • 字符串:用于存储文本,在许多语言中(如 Java 的String)被视为特殊的引用类型。
  • 结构体/类:用于将不同类型的数据封装成一个逻辑单元,是面向对象编程的基石。
  • 集合:如列表、字典、集合等,用于动态存储和管理数据。

二、变量

了解清楚了类型,我们使用类型做什么呢?类型是用来创建变量的。

什么是变量呢?C语言中把经常变化的值称为变量,不变的称为常量。

1. 变量的创建与命名规则

变量是程序中用于存储和表示数据的基本单元。你可以将变量理解为一个有名字的“盒子”,用来存放特定类型的数据。

1.1 变量的创建(声明与初始化)

在大多数编程语言中,创建一个变量通常需要两个步骤:

1.声明(Declaration):告诉编译器/解释器变量的类型和名称。

2.初始化(Initialization):为变量赋予一个初始值。

这两个步骤可以分开进行,也可以合并为一步。

示例(以 C/Java 风格为例):

1.2 变量的命名规则

为了代码的可读性和可维护性,变量的命名需要遵循一定的规则和约定:

  • 基本规则(通常由语言强制规定)
    • 只能包含字母(A-Z, a-z)、数字(0-9)和下划线(_)。
    • 不能以数字开头。
    • 不能是语言的关键字(如int,if,for)。
    • 区分大小写(myVarmyvar是两个不同的变量)。
    • 长度不超过63个字符。
  • 命名约定(最佳实践)
    • 见名知意:变量名应清晰描述其用途(如student_count优于sc)。
    • 风格统一
      • 小驼峰式(camelCase):如firstName,totalAmount(Java, JavaScript 常用)。
      • 蛇形命名(snake_case):如first_name,total_amount(Python, C 常用)。
      • 帕斯卡命名(PascalCase):如FirstName,TotalAmount(常用于类名)。
    • 避免使用单个字母(除了循环计数器i,j,k等)。

2. 变量的作用域:全局变量与局部变量

根据变量在程序中声明的位置,其可被访问的范围(即作用域)不同,主要分为全局变量和局部变量。

2.1 全局变量

在括号外部定义的变量就是全局变量。

特点

  • 从声明处开始,到整个程序文件结束都有效。
  • 可以被该文件内的所有函数、代码块访问和修改。
  • 生命周期与程序运行周期相同。

2.2 局部变量

再大括号内部声明的变量称为局部变量。

特点

  • 只在声明它的函数或代码块内部有效。
  • 外部无法直接访问。
  • 生命周期短暂,通常在函数调用结束时或离开代码块时被销毁。
注意:局部优先

2.3全局变量和局部变量在内存中存储的位置

变量创建的本质是在内存中开辟空间,一般我们在学习C/C++语言的时候,我们会关注内存中的三个区域:栈区、堆区、静态区。

  • 局部变量是放在内存的栈区
  • 全局变量是放在内存的静态区
  • 堆区是用来动态内存管理的

2.4 对比与注意事项

特性全局变量局部变量
声明位置所有函数/代码块之外函数或代码块内部
作用域整个程序文件声明它的函数/代码块内部
生命周期程序开始到结束函数/代码块开始到结束
内存区域通常位于静态存储区或全局数据区通常位于栈(Stack)内存
默认初始值通常自动初始化为零值(如 0, NULL)不会自动初始化,值是未定义的(垃圾值)
使用建议谨慎使用,避免过度使用导致代码耦合度高、难以维护。优先使用,有助于封装和减少副作用。

核心原则:在满足功能需求的前提下,应尽量使用作用域小的局部变量,这有利于模块化、减少命名冲突和内存管理。

三、算数操作符

在编程中,计算是基本且频繁的操作。C语言为了方便各种运算,提供了一系列操作符(也称为运算符)。其中,算术操作符是最基础、最常用的一组,用于执行基本的数学运算。

算术操作符主要包括:+(加)、-(减)、*(乘)、/(除)、%(取模)。这些操作符都是双目操作符,即需要两个操作数(左操作数和右操作数)才能完成运算。

1. 加法与减法操作符:+ 和 -

+-分别用于完成加法和减法运算,其用法与数学中的加法和减法基本一致。

基本用法

操作数可以是变量、常量或表达式。

注意事项

  • 类型转换:当两个操作数类型不同时(如intfloat),会发生隐式类型转换,最终结果的类型通常是“更宽”的类型。
  • 溢出:对于整型,需要注意运算结果可能超出该类型所能表示的范围,导致溢出(未定义行为)。

2. 乘法与除法操作符:* 和 /

*用于乘法,/用于除法。

基本用法

除法操作的特性

  • 整数除法:当两个操作数都是整数时,/执行的是整数除法,结果会截断小数部分,只保留整数部分。
  • 浮点数除法:如果至少有一个操作数是浮点数(floatdouble),则执行浮点数除法,结果保留小数。

注意事项

  • 除以零:在 C 语言中,整数除以零会导致运行时错误(通常是程序崩溃)。浮点数除以零可能会得到无穷大(inf)或 NaN(非数字),具体取决于编译器和标准。

3. 取模操作符:%

%操作符用于计算两个整数相除后的余数。它只适用于整数类型,不能用于浮点数。

基本用法

常见用途

  • 判断奇偶性:if (num % 2 == 0)表示num是偶数。
  • 循环数组索引:index = (index + 1) % array_size可实现循环访问。
  • 数字拆分:获取一个整数的个位数等。

注意事项

  • 取模运算的结果符号与被除数(左操作数)的符号相同
  • 右操作数不能为零,否则会导致运行时错误。

4. 算术操作符的优先级与结合性

当表达式中包含多个操作符时,需要遵循优先级和结合性规则来确定运算顺序。

优先级(从高到低)

  1. */%(乘法、除法、取模)
  2. +-(加法、减法)

结合性

算术操作符的结合性都是从左到右

示例

当优先级相同时,按从左到右的顺序计算。

四、 赋值操作符:=和复合赋值

1. 连续赋值

赋值操作符也可以连续赋值,如:

C语言虽然支持着这种连续赋值,但是写出的代码不易理解,建议还是拆开来写,这样方便观察代码的执行细节。

这样写,在调试时,每一次的赋值的细节都是可以很方便的观察的。

2. 复合赋值

C语言提供了将算术操作与赋值结合的复合赋值操作符,可以使代码更简洁。

操作符示例等价于含义
+=a += 5;a = a + 5;加后赋值
-=b -= 3;b = b - 3;减后赋值
*=c *= 2;c = c * 2;乘后赋值
/=d /= 4;d = d / 4;除后赋值
%=e %= 7;e = e % 7;取模后赋值
<<=x <<= 2;x = x << 2;左移后赋值
>>=y >>= 1;y = y >> 1;右移后赋值
&=z &= 0xFF;z = z & 0xFF;按位与后赋值
|=w |= 0x01;w = w | 0x01;按位或后赋值
^=v ^= 0x0F;v = v ^ 0x0F;按位异或后赋值

五、单目操作符:++ 、-- 、+ 、-

前面介绍的操作符都是双目操作符,有两个操作数的。C语言中还有一些操作符只有一个操作数,被称为单目操作符。++ 、-- 、+(正)、-(负)就是单目操作符。

1. ++和--

++(自增)和--(自减)是单目操作符,用于将变量的值增加 1 或减少 1。它们有前缀和后缀两种形式,行为略有不同。

前缀形式(先增减,后使用)

后缀形式(先使用,后增减)

注意事项

  • 避免在同一个表达式中对同一个变量多次使用自增/自减操作符,其结果是未定义行为(如i = i++;)。
  • 常用于循环控制变量(如for (int i = 0; i < n; i++))。

2. + 和 -

这里的 + 是正号,- 是负号,都是单目操作符。

运算符 + 对正负没有影响,是一个完全可以省略的运算符,但是写了也不会报错。

运算符 - 用来改变一个值的正负,负数的前面加上 - 就会得到证书,正数的前面加上 - 会得到负数。

六、强制类型转换

在操作符中还有一种特殊的操作符是强制类型转换。在编程中,我们经常需要在不同类型的数据之间进行转换。当编译器无法自动完成这种转换,或者我们需要明确指定转换方式时,就需要使用强制类型转换(Type Casting)。

语法形式很简单,形式如下:

语法通常是在要转换的值或变量前加上目标类型,并用括号括起来:

示例1:浮点数转整数

示例2:整数除法得到浮点数结果

示例3:字符与整数的转换

注意:强制类型转换是一把双刃剑。它提供了强大的灵活性,允许程序员跨越类型系统的限制,但也带来了数据丢失、未定义行为等风险。基本原则是:尽量避免不必要的强制转换;如果必须使用,请确保你完全理解转换的后果 。

  • 数据丢失(截断):从浮点型转换为整型时,小数部分会被直接丢弃,而不是四舍五入。
  • 值域溢出:如果转换后的值超出了目标类型的表示范围,结果将是未定义的(通常会发生溢出)。
  • 符号问题:在有符号和无符号整数之间转换时,负数的表示会发生变化。
  • 指针转换的危险性:不相关的指针类型之间的强制转换(如int*float*)可能导致程序崩溃或产生难以调试的错误。

七、scanf 和 printf 的介绍

在 C 语言中,scanfprintf是两个最基础、最常用的输入输出函数,它们都定义在标准输入输出头文件<stdio.h>中。它们是程序与用户交互的桥梁,也是调试和展示结果的重要工具。

1. printf 函数:格式化输出

1.1 基本用法

printf()的作用是将参数文本输出到屏幕。它名字里面的f代表format(格式化),表示可以定制输出文本的格式,包含要输出的普通文本和格式控制符(以%开头)。

上面命令会在0屏幕上输出一行文字“Hellow World”

printf()不会再行尾自动添加换行符,运行结束后,光标停在输出结束的地方,不会自动换行。 为了让光标移到下一行的开头,可以在输出文本的结尾,添加一个换行符\n。

如果内部有换行,也是通过插入换行符来实现的,如下方代码:

printf()是在标准库的头文件stdio.h定义的。使用这个函数之前,必须在源代码文件头部引入这个头文件。

1.2 占位符

printf()可以在输出文本中指定占位符。

  • 所谓“占位符”,就是这个位置可以用其他值代入。

上面实例中,There are %d apple \n 是输出文本,里面的 %d 就是占位符,表示这个位置要用其它值来代替。占位符的第一个字符一律为 % ,第二个字符表示占位符的类型,%d 表示这里带入的值必须是一个10进制的整数。

printf()的第二个参数就是替换占位符的值。上面的例子是整数3替换%d 。执行后输出结果就是There are 3 apple。

  • 输出文本里面可以使用多个占位符

上面示例中,输出文本 %s says it is %d o'clock 有两个占位符,第一个占位符是字符串占位符 %s ,第二个是整数占位符 %d ,分别对应printf()的第二个参数(lisi)和第三个参数(21)。执行后的输出是 lisi says it is 21 o'clock。

printf()参数与占位符是一一对应的关系,如果有n个占位符,printf()的参数就应该有n+1个。如果参数个数少于对应的占位符,printf()可能会输出内存中的任意值。

1.3 占位符举例

格式控制符说明对应类型示例
%a/%A输出十六进制浮点数(小写/大写)double,floatprintf("%a", 3.14);
%c输出单个字符charprintf("%c", 'A');
%d输出有符号十进制整数intprintf("%d", 42);
%e/%E输出科学计数法浮点数(小写e/大写E)double,floatprintf("%e", 3.14);
%f输出十进制浮点数(默认保留6位小数)double,floatprintf("%f", 3.14);
%g/%G根据值自动选择%f%e格式(小写/大写)double,floatprintf("%g", 3.14);
%hd输出有符号短整型short intprintf("%hd", 32767);
%ho输出无符号短整型八进制unsigned short intprintf("%ho", 65535);
%hx/%hX输出无符号短整型十六进制(小写/大写)unsigned short intprintf("%hx", 65535);
%hu输出无符号短整型十进制unsigned short intprintf("%hu", 65535);
%ld输出有符号长整型long intprintf("%ld", 1000000L);
%lo输出无符号长整型八进制unsigned long intprintf("%lo", 4000000000UL);
%lx/%lX输出无符号长整型十六进制(小写/大写)unsigned long intprintf("%lx", 4000000000UL);
%lu输出无符号长整型十进制unsigned long intprintf("%lu", 4000000000UL);
%lld输出有符号长长整型long long intprintf("%lld", 10000000000LL);
%llo输出无符号长长整型八进制unsigned long long intprintf("%llo", 10000000000ULL);
%llx/%llX输出无符号长长整型十六进制(小写/大写)unsigned long long intprintf("%llx", 10000000000ULL);
%llu输出无符号长长整型十进制unsigned long long intprintf("%llu", 10000000000ULL);
%Le/%LE输出长双精度科学计数法浮点数long doubleprintf("%Le", 3.14L);
%Lf输出长双精度十进制浮点数long doubleprintf("%Lf", 3.14L);
%n将已输出的字符数写入指针参数int *int count; printf("Hello%n", &count);
%o输出八进制整数unsigned intprintf("%o", 64);
%p输出指针地址(十六进制)void *printf("%p", &var);
%s输出字符串char *printf("%s", "Hello");
%u输出无符号十进制整数unsigned intprintf("%u", 100u);
%x/%X输出十六进制整数(小写/大写)unsigned intprintf("%x", 255);
%zu输出size_t类型无符号整数size_tprintf("%zu", sizeof(int));
%%输出百分号字符无(字面量)printf("完成度: 50%%");

1.4 输出格式

printf()可以定制占位符的输出格式

1.4.1 限定宽度
  • printf()允许限定占位符的最小宽度

示例一:

上面示例输出“ 123”,%5d表示这个占位符的宽度至少为5位。如果不满5位,对应的值起那面会添加空格。

输出的值默认是右对齐,及输出内容前面会有空格;如果希望改成左对齐,在输出内容后面添加空格,可以在占位符的%的后面输入一个 - 号。

上面示例输出“123 ”,后面添加了空格。

示例二:

对于小数,这个限定符会限制所有小数的最小显示宽度。

%f 在打印小数的时候,默认小数点后6位。

上面示例输出“ 123.450000”,%12表示输出的浮点数在最少要占据12位。由于小数的默认显示精度是小数点后6位,所以输出结果头部会添加2个空格。

1.4.2 总是显示正负号

默认情况下,printf()不对正数显示 + 号,只对负数显示 - 号。如果想要正数也输出 + 号,可以在占位符的 % 后面加一个 + 。

上面的示例中,%+d 可以确保输出的数值,总是带有正负号。

1.4.3 限定小数位数

示例一:输出小数时,有时希望限定小数位数。举例来说,希望小数点后面只保留2位,占位符可以写成%.2f。

这种写法可以与限定宽度占位符,结合使用。

上面示例中,%6.2f表示输出字符串最小宽度为6,小数位为2。所以,输出字符串的头部有两个空格。

示例二:最小宽度和小数位数这两个限定值,都可以用 * 代替,通过printf()的参数传入。

上面示例中,%*,*f 的两个星号通过 printf() 的两个参数 6 和 2 传入。

1.4.4 输出部分字符串

%s 占位符用来输出字符串,默认是全部输出。如果只想输出开头部分,可以用 %.[m]s 指定是输出长度,其中 [m] 代表一个数字,表示所要输出的长度。

上面实例中,占位符 %.5s 表示只输出字符串“hellow world”的钱5个字符,即“hellow”。

2. scanf 函数:格式化输入

当我们有了变量,我们需要给变量输入值就可以使用scanf函数,如果需要将变量的值输出在屏幕上的时候可以使用printf函数

编译时常见的出错情况

2.1 基本用法

scanf()函数用于读取用户的键盘输入。程序运行到这个语句时,会停下,等待用户从键盘输入。用户输入数据,按下回车后,scanf()就会处理用户的输入,将其存入变量。

它的原型定义在头文件stdio.h。

scanf()的语法与printf()类似。

scanf()处理用户输入的原理是,用户的输入先放入缓存,等到按下回车键后,按照占位符对缓存进行解读。解读用户输入时,会从上一次解读遗留的第一个字符开始,知道读完缓存,或者遇到第一个不符合条件的字符为止。

2.2 scanf 的返回值(printf:返回成功输出的字符数,失败时返回负值。)

scanf()的返回值是一个整数,表示成功读取的变量个数,可用于验证输入是否合法。

如果没有读取任何项,或者匹配失败,则返回 0 。

如果在成功读取任何数据之前,发生了读取错误或者遇到读取到文件结尾,则返回常量 EOF(-1)。EOF - end of file 文件结束标志(Windows:Ctrl+Z;Linux/Mac:Ctrl+D

示例一:

示例二:

示例三:

示例四:

示例五:

2.3 占位符(与 printf 类似)

格式控制符说明示例
%c读取单个字符(包括空格、换行)scanf("%c", &ch);
%d读取有符号十进制整数scanf("%d", &num);
%f读取单精度浮点数(float类型)scanf("%f", &score);
%lf读取双精度浮点数(double类型)scanf("%lf", &price);
%Lf读取长双精度浮点数(long double类型)scanf("%Lf", &ld_val);
%s读取字符串(遇到空格、制表符、换行停止)scanf("%s", name);
%[]扫描字符集合,读取匹配指定字符集的字符串scanf("%[a-zA-Z]", letters);

注意:

  • 上面所有占位符之中,除了 % c 以外,都会自动忽略起首的空白字符。% c 不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。
  • 如果要强制跳过字符前的空白字符,可以写成 scanf ("% c", &ch),即 % c 前加上一个空 格,表示跳过零个或多个空白字符。

  • 下面要特别说一下占位符 % s,它其实不能简单地等同于字符串。它的规则是,从当前第一个非空白字符开始读起,直到遇到空白字符(即空格、换行符、制表符等)为止。
  • 因为 % s 不会包含空白字符,所以无法用来读取多个单词,除非多个 % s 一起使用。这也意味着,scanf () 不适合读取可能包含空格的字符串,比如书名或歌曲名。另外,scanf () 遇到 % s 占位符,会在字符串变量末尾存储一个空字符 \0。

  • scanf () 将字符串读入字符数组时,不会检测字符串是否超过了数组长度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防止这种情况,使用 % s 占位符时,应该指定读入字符串的最长长度,即写成 %[m] s,其中的 [m] 是一个整数,表示读取字符串的最大长度,后面的字符将被丢弃。

2.4 赋值忽略符

有时,用户的输入可能不符合预定的格式

解决方法: