第二讲: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 内置数据类型
内置数据类型是不可再分的最小数据单元。它们通常直接存储在栈内存中,操作速度快。
- 整型:用于表示整数,如
int、short、long。在 C/C++ 等语言中,整型还可以分为有符号(signed)和无符号(unsigned)两种。有符号整型可以表示正数、负数和零,而无符号整型只能表示非负数(零和正数),因此其正数表示范围更大。 - 浮点型:用于表示带小数点的数字,如
float、double。 - 字符型:用于表示单个字符,如
char。字符类型存储的时候是存储ASCII值,ASCII值是整数,所以字符类型是属于整型家族的。 - 布尔型:用于表示逻辑真或假,只有两个值:
true和false。
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)。 - 区分大小写(
myVar和myvar是两个不同的变量)。 - 长度不超过63个字符。
- 命名约定(最佳实践):
- 见名知意:变量名应清晰描述其用途(如
student_count优于sc)。 - 风格统一:
- 小驼峰式(camelCase):如
firstName,totalAmount(Java, JavaScript 常用)。 - 蛇形命名(snake_case):如
first_name,total_amount(Python, C 常用)。 - 帕斯卡命名(PascalCase):如
FirstName,TotalAmount(常用于类名)。
- 小驼峰式(camelCase):如
- 避免使用单个字母(除了循环计数器
i,j,k等)。
- 见名知意:变量名应清晰描述其用途(如
2. 变量的作用域:全局变量与局部变量
根据变量在程序中声明的位置,其可被访问的范围(即作用域)不同,主要分为全局变量和局部变量。
2.1 全局变量
在括号外部定义的变量就是全局变量。
特点:
- 从声明处开始,到整个程序文件结束都有效。
- 可以被该文件内的所有函数、代码块访问和修改。
- 生命周期与程序运行周期相同。
2.2 局部变量
再大括号内部声明的变量称为局部变量。
特点:
- 只在声明它的函数或代码块内部有效。
- 外部无法直接访问。
- 生命周期短暂,通常在函数调用结束时或离开代码块时被销毁。
注意:局部优先
2.3全局变量和局部变量在内存中存储的位置
变量创建的本质是在内存中开辟空间,一般我们在学习C/C++语言的时候,我们会关注内存中的三个区域:栈区、堆区、静态区。
- 局部变量是放在内存的栈区
- 全局变量是放在内存的静态区
- 堆区是用来动态内存管理的
2.4 对比与注意事项
| 特性 | 全局变量 | 局部变量 |
|---|---|---|
| 声明位置 | 所有函数/代码块之外 | 函数或代码块内部 |
| 作用域 | 整个程序文件 | 声明它的函数/代码块内部 |
| 生命周期 | 程序开始到结束 | 函数/代码块开始到结束 |
| 内存区域 | 通常位于静态存储区或全局数据区 | 通常位于栈(Stack)内存 |
| 默认初始值 | 通常自动初始化为零值(如 0, NULL) | 不会自动初始化,值是未定义的(垃圾值) |
| 使用建议 | 谨慎使用,避免过度使用导致代码耦合度高、难以维护。 | 优先使用,有助于封装和减少副作用。 |
核心原则:在满足功能需求的前提下,应尽量使用作用域小的局部变量,这有利于模块化、减少命名冲突和内存管理。
三、算数操作符
在编程中,计算是基本且频繁的操作。C语言为了方便各种运算,提供了一系列操作符(也称为运算符)。其中,算术操作符是最基础、最常用的一组,用于执行基本的数学运算。
算术操作符主要包括:+(加)、-(减)、*(乘)、/(除)、%(取模)。这些操作符都是双目操作符,即需要两个操作数(左操作数和右操作数)才能完成运算。
1. 加法与减法操作符:+ 和 -
+和-分别用于完成加法和减法运算,其用法与数学中的加法和减法基本一致。
基本用法
操作数可以是变量、常量或表达式。
注意事项
- 类型转换:当两个操作数类型不同时(如
int和float),会发生隐式类型转换,最终结果的类型通常是“更宽”的类型。 - 溢出:对于整型,需要注意运算结果可能超出该类型所能表示的范围,导致溢出(未定义行为)。
2. 乘法与除法操作符:* 和 /
*用于乘法,/用于除法。
基本用法
除法操作的特性
- 整数除法:当两个操作数都是整数时,
/执行的是整数除法,结果会截断小数部分,只保留整数部分。 - 浮点数除法:如果至少有一个操作数是浮点数(
float或double),则执行浮点数除法,结果保留小数。
注意事项
- 除以零:在 C 语言中,整数除以零会导致运行时错误(通常是程序崩溃)。浮点数除以零可能会得到无穷大(
inf)或 NaN(非数字),具体取决于编译器和标准。
3. 取模操作符:%
%操作符用于计算两个整数相除后的余数。它只适用于整数类型,不能用于浮点数。
基本用法
常见用途
- 判断奇偶性:
if (num % 2 == 0)表示num是偶数。 - 循环数组索引:
index = (index + 1) % array_size可实现循环访问。 - 数字拆分:获取一个整数的个位数等。
注意事项
- 取模运算的结果符号与被除数(左操作数)的符号相同。
- 右操作数不能为零,否则会导致运行时错误。
4. 算术操作符的优先级与结合性
当表达式中包含多个操作符时,需要遵循优先级和结合性规则来确定运算顺序。
优先级(从高到低)
*、/、%(乘法、除法、取模)+、-(加法、减法)
结合性
算术操作符的结合性都是从左到右。
示例
当优先级相同时,按从左到右的顺序计算。
四、 赋值操作符:=和复合赋值
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 语言中,scanf和printf是两个最基础、最常用的输入输出函数,它们都定义在标准输入输出头文件<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,float | printf("%a", 3.14); |
%c | 输出单个字符 | char | printf("%c", 'A'); |
%d | 输出有符号十进制整数 | int | printf("%d", 42); |
%e/%E | 输出科学计数法浮点数(小写e/大写E) | double,float | printf("%e", 3.14); |
%f | 输出十进制浮点数(默认保留6位小数) | double,float | printf("%f", 3.14); |
%g/%G | 根据值自动选择%f或%e格式(小写/大写) | double,float | printf("%g", 3.14); |
%hd | 输出有符号短整型 | short int | printf("%hd", 32767); |
%ho | 输出无符号短整型八进制 | unsigned short int | printf("%ho", 65535); |
%hx/%hX | 输出无符号短整型十六进制(小写/大写) | unsigned short int | printf("%hx", 65535); |
%hu | 输出无符号短整型十进制 | unsigned short int | printf("%hu", 65535); |
%ld | 输出有符号长整型 | long int | printf("%ld", 1000000L); |
%lo | 输出无符号长整型八进制 | unsigned long int | printf("%lo", 4000000000UL); |
%lx/%lX | 输出无符号长整型十六进制(小写/大写) | unsigned long int | printf("%lx", 4000000000UL); |
%lu | 输出无符号长整型十进制 | unsigned long int | printf("%lu", 4000000000UL); |
%lld | 输出有符号长长整型 | long long int | printf("%lld", 10000000000LL); |
%llo | 输出无符号长长整型八进制 | unsigned long long int | printf("%llo", 10000000000ULL); |
%llx/%llX | 输出无符号长长整型十六进制(小写/大写) | unsigned long long int | printf("%llx", 10000000000ULL); |
%llu | 输出无符号长长整型十进制 | unsigned long long int | printf("%llu", 10000000000ULL); |
%Le/%LE | 输出长双精度科学计数法浮点数 | long double | printf("%Le", 3.14L); |
%Lf | 输出长双精度十进制浮点数 | long double | printf("%Lf", 3.14L); |
%n | 将已输出的字符数写入指针参数 | int * | int count; printf("Hello%n", &count); |
%o | 输出八进制整数 | unsigned int | printf("%o", 64); |
%p | 输出指针地址(十六进制) | void * | printf("%p", &var); |
%s | 输出字符串 | char * | printf("%s", "Hello"); |
%u | 输出无符号十进制整数 | unsigned int | printf("%u", 100u); |
%x/%X | 输出十六进制整数(小写/大写) | unsigned int | printf("%x", 255); |
%zu | 输出size_t类型无符号整数 | size_t | printf("%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 赋值忽略符
有时,用户的输入可能不符合预定的格式
解决方法:
