数据类型和变量
文章目录
数据类型和变量
- C语言中提供了丰富的数据类型来描述生活中的数据。例如,使用整型类型来描述整数,用字符类型描述字符,用浮点数描述小数。之所以使用不同的数据类型是因为这样有便于编译器知道数据的类型更好的操作数据。
- 我们用数据类型来创建相应的变量,把经常变化的值称为变量,不变的值称为常量。
1.数据类型的分类
1.1字符型
char //character
[signed] char //有符号的
[unsigned] char //无符号的
1.2整型
char //character
[signed] char //有符号的
[unsigned] char //无符号的
//短整型
short [int]
[signed] short [int] //[int]可省略
[unsigned] short [int]//[int]可省略
//整型
int
[signed] int
[unsigned] int
//长整型
long [int]//[int]可省略
[signed] long [int]//[int]可省略
//更长的整型(C99中引入)
long long [int]//[int]可省略
[signed] long long [int]//[int]可省略
unsigned long long [int]//[int]可省略
1.3浮点型
float //单精度浮点型
double //双精度浮点型
long double //长精度浮点型
1.4布尔型
在C99中引入了布尔型
,专门表示真假。原来C语言中都用整数0表示假,非零表示真
_Bool
- 布尔类型的使用需要包含头文件
<stdlib.h>
,其中布尔类型变量的取值是true
和false
#define bool _Bool
#define false 0
#define true 1
//代码演示
_Bool flag = true
if(flag)
printf("i like C\n")
1.5各种数据类型的长度
- 使用不同的数据类型,能够创建出长度不同的变量,变量长度不同,储存的数据类型范围有所不同
1.5.1 sizeof操作符
sizeof
是一个关键字,同时也是一个操作符,专门用来计算类型长度的,单位是字节sizeof
的操作符的操作数可以是类型,也可以是 变量或者表达式
sizeof(类型)
sizeof(表达式)
sizeof
的操作数是表达式时可以省略括号sizeof
后面的表达式是不参与真实运算的,是根据表达式的类型得出的大小sizeof
的计算结果是size_t
类型的
注意:sizeof
运算符的返回值,C语⾔只规定是⽆符号整数,并没有规定具体的类型,⽽是留给
系统⾃⼰去决定,sizeof
到底返回什么类型。不同的系统中,返回值的类型有可能是
unsigned int
,也有可能是unsigned long
,甚⾄是unsigned long long
,
对应的printf()
占位符分别是%u
、%lu
和%llu
。这样不利于程序的可移植性。
C语⾔提供了⼀个解决⽅法,创造了⼀个类型别名size_t
,⽤来统⼀表示sizeof
的返
回值类型。对应当前系统的sizeof
的返回值类型,可能是unsigned int
,也可能是
unsigned long long
#include <stdio.h>
int main(){
int a = 10;
printf("%zd\n",sizeof(a));
printf("%zd\n",sizeof a);
printf("%zd\n",sizeof (int));
printf("%zd\n",sizeof (3 + 3.5));
return 0;
}
1.5.2 数据类型长度
#include <stdio.h>
int main() {
printf("%zd\n",sizeof(char));
printf("%zd\n", sizeof(_Bool));
printf("%zd\n", sizeof(short));
printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof(long));
printf("%zd\n", sizeof(long long));
printf("%zd\n", sizeof(float));
printf("%zd\n", sizeof(double));
printf("%zd\n", sizeof(long double));
return 0;
}
1.5.4 sizeof 表达式不计算问题
#include <stdio.h>
int main() {
short a = 2;
int b = 10;
printf("%d\n",sizeof(a = b+1));
printf("a = %d\n",a);
return 0;
}
2.signed和unsigned
- C语言中signed 和 unsigned 关键字分别修饰字符型和整型类型。
- signed 关键字,表示⼀个类型带有正负号,包含负值;
- unsigned 关键字,表示该类型不带有正负号,只能表⽰零和正整数。
- 对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int 。由于这是默认情况,关键字 signed ⼀般都省略不写,但是写了也不算错。
- 区别:
- 用
unsigned
的好处是,同样长度的内存能够表示的最大整数值增加了一倍,比如:16位的signed short int
的取值范围是:-32768~32767,最大是32767;而unsigned short int
的取值范围是:0~65535,最大值增大到了65535。
- 用
3.变量的创建
- 变量创建的语法形式:
data_type name;//data_type表示数据类型,name是变量名
int age; //整型变量
char ch; //字符变量
double weight; //浮点型变量
- 变量的初始化:
int age = 18;
char ch = 'w';
double weight = 48.0;
unsigned int height = 100
4.变量的分类
- 全局变量:在大括号外的定义的变量
- 全局变量的使用范围更广,整个工程中想要使用都可以;
- 局部变量:在大括号内定义的变量
- 局部变量的使用范围比较局限,只能在自己的局部范围内使用
#include <stdio.h>
int global = 2024;//全局变量
int main()
{
int local = 2005;//局部变量
printf("%d\n", local);
printf("%d\n", global);
return 0;
}
在这里插入图片描述
#include <stdio.h>
int a = 2024;//全局变量
int main()
{
int a = 2005;//局部变量
printf("%d\n", a);
return 0;
}
-所以当局部变量和全局变量同名时,局部变量优先使用
拓展:学习C/C++的时候,我们会经常关注内存中这三个区域:栈区,堆区,静态区。
简单来说就是:
1. 局部变量是放在内存的栈区的
2. 全局变量是放在内存的静态区的
3. 堆区时用来动态内存管理的
5.算数操作符
- C语言中为了方便计算,提供了一系列的操作符(运算符),其中一组操作符叫:算术操作符,分别是:+,-,*,/,%,这些操作符也叫:双目操作符
5.1+
和-
+
和-
用来完成加法和减法;- 它们之所以叫双目运算符,是因为它们左右两端一共有两个操作数;如:
#include <stdio.h>
int main()
{
int x = 5 + 20;
int y = 60 - 20;
printf("%d\n", x);
printf("%d\n", y);
return 0;
}
5.2*
5.3/
注意:
- 除号两端如果是整数,执行的是整数除法,得到的结果也是整数
- 如果想要得到浮点数结果,那么两个运算数必须至少有一个浮点数,此时就会进行浮点数除法
尽管比变量x
的类型是float
类型,但是计算结果依然是1.0
,而不是1.5
.原因就在于C语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。
这时6.0/4
表示的是浮点数除法,得到的是1.5
5.4%
- 运算符
%
表示求模运算,即**返回两个整数相除的余值。这个运算符只能用于整数**,不能用于浮点数 - 负数求模的规则是,结果的正负号由**第⼀个运算数的正负号**决定
6.赋值操作符:=
和复合赋值
- 为变量首次分配一个值的过程被称为初始化。而在变量创建后,若需要改变其原有的值,我们则使用赋值操作。赋值操作符
=
就是用来给变量赋予新值的工具,它允许我们在任何时刻为变量重新赋值。
6.1连续赋值
下面的代码展示了如何在C语言中为多个变量进行赋值:
#include <stdio.h>
int main(){
int a, b, c;
a = 10; // 为变量a赋值
b = a; // 将变量a的值赋给变量b
c = b = a + 3; // 连续赋值,从左到右依次赋值
return 0;
}
在C语言中虽然可以连续赋值,但是不建议这样书写,因为写出的代码不易理解不说,还不容易观察代码细节(例如:在调试过程中)
6.2复合赋值
- 在写代码时,我们经常可能对⼀个数进⾏⾃增、⾃减的操作,如下代码
int a = 10;
a = a+3;
a = a-2
C语言提供了更加方便的写法:
int a = 10;
a += 3;
a -= 2
C语言中提供了复合赋值符,方便我们编写代码,这些赋值符有:
+= | -= | —— |
---|---|---|
*= | /= | %= |
>>= | <<= | —— |
&= | = | ^= |
7.单目操作符
- C语⾔中还有⼀些操作符只有⼀个操作数,被称为单目操作符。 ++、–、+(正)、-(负) 就是单目操作符的。
7.1 ++
和--
++
是⼀种自增的操作符,又分为前置++
和后置++
,–是⼀种自减的操作符,也分为前置--
和后置--
前置++:
计算口诀:先+1,后使用;
int a = 10;
int b = ++a;
printf("a = %d,b = %d\n",a,b);
a原来是10,先加1,变成11,然后赋值给b,b就是11;
后置++
计算口诀:先使用,后+1;
int a = 10;
int b = a++;
printf("a = %d,b = %d\n",a,b);
a原来是10,先使用,将10先赋值给b,b变成10,然后啊在+1变成11;
前置-
-和后置--
同理
7.2 +
和-
这里的+
,-
表示正负号
- 正号运算符 (+):
#include <stdio.h>
int main() {
int x = -5;
int y = +x; // y 的值也是 -5,因为正号不会改变数的符号
printf("y = %d\n", y);
return 0;
}
在这个例子中,+x
表达式不会对 x
的值产生任何影响,只是简单地返回 x
的值。
- 负号运算符 (-):
#include <stdio.h>
int main() {
int x = 5;
int y = -x; // y 的值是 -5
printf("y = %d\n", y);
return 0;
}
在这个例子中,-x
表达式会返回 x
的相反数。
8.强制类型转换
-
在C语言中,强制类型转换是一种显式地改变变量或表达式类型的方法。强制类型转换允许程序员将一种数据类型的值转换为另一种数据类型的值。这种转换有时是必要的,特别是在处理不同类型的数据混合运算时,或者当需要将一个变量存储为另一种类型时。
-
强制类型转换的语法如下:
(type_name) expression
其中 type_name 是你想要转换成的数据类型,expression 是要进行转换的变量或表达式。
- 下面是一些C语言中强制类型转换的例子:
将浮点数转换为整数:
float f = 3.14;
int i = (int)f; // i 现在是 3
将整数转换为浮点数:
int a = 10;
float b = (float)a; // b 现在是 10.0
将字符转换为整数(得到字符的ASCII码):
char ch = 'A';
int ascii_value = (int)ch; // ascii_value 现在是 65('A' 的ASCII码)
将一个指针类型转换为另一个指针类型:
int x = 10;
int *int_ptr = &x;
char *char_ptr = (char *)&x; // 注意:这样的转换可能会导致未定义的行为,除非你确定知道你在做什么
将无符号整数转换为有符号整数(或反之):
unsigned int ui = 4294967295U; // 最大的无符号32位整数
int si = (int)ui; // si 现在是 -1,因为无符号整数的最大值转换为有符号整数时变为负数
注意:强制类型转换可能会导致数据丢失或精度降低,特别是当从范围较大的类型转换为范围较小的类型时(例如从 double 转换为 int)。此外,将指针转换为不兼容的类型可能会导致未定义的行为,因此在执行此类转换时必须格外小心。
在进行强制类型转换时,应始终确保你理解转换的后果,并确保转换是安全且合理的。如果不确定,最好避免不必要的类型转换,并寻求其他解决方案。
9.scanf
和printf
printf
基本用法
- `printf`是一个用于输出格式化字符串的标准库函数。它名字里的`f`表示f`ormat(格式化)`(可以定制输出文本的格式)功能是向标准输出(通常是屏幕)打印文本。`printf`函数由`stdio.h`库提供,因此在使用它之前,你需要在程序顶部包含这个头文件。
- `printf`的**基本语法**如下:
printf(format_string, argument1, argument2, ...);
format_string:一个包含文本和格式说明符的字符串。格式说明符以%开头,后面跟着一个或多个字符,用于指定如何格式化后面的参数。
argument1, argument2, ...:要插入到format_string中的值。这些参数的数量和类型应该与format_string中的格式说明符相匹配。
#include <stdio.h>
int main() {
int a = 5;
float b = 3.14;
printf("整数是:%d,浮点数是:%f\n", a, b);
return 0;
}
在这个例子中,%d
是一个格式说明符(占位符),用于表示一个整数。%f
用于表示一个浮点数。printf
函数将a
和b
的值插入到字符串中的相应位置,并输出到屏幕。
\n
是一个转义字符,代表换行符。当printf
遇到\n
时,它会在输出中插入一个新行,使得后续的文本从下一行开始。这通常用于在打印多行文本时分隔行。
例如:
printf("这是第一行\n");
printf("这是第二行\n");
这段代码会输出两行文本,每行文本后都有一个换行符,使得两行文本分别显示在不同的行上。
如果你省略了\n
,那么printf
会连续输出文本,不会自动换行。例如:
printf("这是第一行");
printf("这是第二行");
这段代码将输出==“这是第一行这是第二行”==,没有换行符分隔两行文本。
占位符
printf
和scanf
等函数使用占位符(也称为格式说明符)来指定如何格式化输入或输出数据。占位符以百分号 % 开头,后面跟着一个或多个字符,这些字符决定了数据的类型、大小、对齐方式等。printf
输出占位符
%d 或 %i | 有符号十进制整数 |
---|---|
%u | 无符号十进制整数。 |
%f | 浮点数。 |
%e 或 %E | 用科学计数法表示的浮点数。 |
%g 或 %G | 根据数值的大小自动选择 %f 或 %e 表示法。 |
%c | 字符。 |
%s | 字符串。 |
%p | 指针地址。 |
%x 或 %X | 无符号十六进制整数(小写或大写)。 |
%o | 无符号八进制整数。 |
%% | 输出一个百分号 %。 |
scanf
输入占位符
%d | 有符号十进制整数。 |
---|---|
%u | 无符号十进制整数。 |
%f | 浮点数。 |
%c | 字符。 |
%s | 字符串(注意:scanf 中的 %s 不会读取空格、制表符或换行符)。 |
%p | 指针地址。 |
%x 或 %X | 无符号十六进制整数(小写或大写)。 |
%o | 无符号八进制整数。 |
- 宽度和精度
占位符还可以包含宽度和精度说明符,用于控制输出或输入的字段宽度和浮点数的小数位数。例如:
%10d:输出一个至少占用10个字符宽度的整数,不足部分用空格填充。
%.2f:输出一个浮点数,保留两位小数。
- 标志
- 可以在占位符前添加标志来修改输出格式,例如:
%-10d
:左对齐输出一个至少占用10个字符宽度的整数。
%+d
:输出有符号整数时总是带上符号(正数也带加号)。
%010d
:输出一个至少占用10个字符宽度的整数,不足部分用零填充。
#include <stdio.h>
int main() {
int a = 123;
float b = 3.14159;
char c = 'A';
char str[] = "Hello, World!";
printf("整数:%d\n", a);
printf("浮点数:%.2f\n", b);
printf("字符:%c\n", c);
printf("字符串:%s\n", str);
printf("十六进制整数:%x\n", a);
printf("宽度和精度:%10.2f\n", b);
return 0;
}
在 printf
中,如果提供的参数与占位符不匹配,可能会导致未定义的行为或运行时错误。因此,确保占位符与提供的参数类型一致是非常重要的。
占位符列举
%a | ⼗六进制浮点数,字⺟输出为⼩写。 |
---|---|
• %A | ⼗六进制浮点数,字⺟输出为⼤写。 |
• %c | 字符。 |
• %d | ⼗进制整数。 |
• %e | 使⽤科学计数法的浮点数,指数部分的 e 为⼩写。 |
• %E | 使⽤科学计数法的浮点数,指数部分的 E 为⼤写。 |
• %i | 整数,基本等同于 %d 。 |
• %f | ⼩数(包含 float 类型和 double 类型)。 |
• %g | 6个有效数字的浮点数。整数部分⼀旦超过6位,就会⾃动转为科学计数法,指数部分的 e为小写。 |
• %G | 等同于 %g ,唯⼀的区别是指数部分的 E 为⼤写。 |
• %hd | ⼗进制 short int 类型。 |
• %ho | ⼋进制 short int 类型。 |
• %hx | ⼗六进制 short int 类型。 |
• %hu | unsigned short int 类型。 |
• %ld | ⼗进制 long int 类型。 |
• %lo | ⼋进制 long int 类型。 |
• %lx | ⼗六进制 long int 类型。 |
• %lu | unsigned long int 类型。 |
• %lld | ⼗进制long long int 类型。 |
• %llo | ⼋进制 long long int类型。 |
• %llx | ⼗六进制 long long int 类型。 |
• %llu | unsigned long long int 类型。 |
• %Le | 科学计数法表示的 long double 类型浮点数。 |
• %Lf | long double 类型浮点数。 |
• %n | 已输出的字符串数量。该占位符本⾝不输出,只将值存储在指定变量之中。 |
•%o | ⼋进制整数。 |
• %p | 指针。 |
• %s | 字符串。 |
• %u | 无符号整数(unsigned int)。 |
• %x | ⼗六进制整数。 |
• %zd | size_t 类型。 |
• %% | 输出⼀个百分号。 |
scanf
- scanf 的基本语法可以总结为以下格式:
int scanf(const char *format, ...);
下面是一些 scanf 的基本用法示例:
- 读取整数
int num;
printf("请输入一个整数: ");
scanf("%d", &num);
printf("你输入的整数是: %d\n", num);
- 读取浮点数
float f;
printf("请输入一个浮点数: ");
scanf("%f", &f);
printf("你输入的浮点数是: %f\n", f);
- 读取字符
char ch;
printf("请输入一个字符: ");
scanf(" %c", &ch); // 注意前面的空格,用于跳过任何之前的空白字符(如空格、制表符或换行符)
printf("你输入的字符是: %c\n", ch);
- 读取字符串
char str[100];
printf("请输入一个字符串: ");
scanf("%s", str); // 注意这里不需要 &,因为数组名本身就是地址
printf("你输入的字符串是: %s\n", str);
- 读取多个值
int a, b;
printf("请输入两个整数: ");
scanf("%d %d", &a, &b); // 可以一次性读取多个值,值之间用空格分隔
printf("你输入的两个整数是: %d 和 %d\n", a, b);
注意:
1.格式字符串:scanf
的第一个参数是一个格式字符串,它指定了如何解析输入。例如,%d
用于整数,%f
用于浮点数,%c
用于字符,%s
用于字符串。
2.变量地址:scanf
需要知道将读取的值存储在哪里,因此你需要传递变量的地址。对于基本数据类型(如 int、float、char
),你需要使用== & 运算符==来获取变量的地址。但是,对于数组(如字符串),数组名本身就是地址,所以不需要 &。
3.缓冲区问题:scanf
在读取字符串时不会检查缓冲区溢出,因此如果输入的字符串超过了目标数组的大小,可能会导致缓冲区溢出。为了避免这种情况,可以使用其他安全的输入函数,如fgets
。
4.空格和换行符:scanf
会自动跳过前导的空白字符(如空格、制表符和换行符),除非在格式字符串中显式指定了它们。例如,在读取字符时,你可能需要在格式字符串前加一个空格来跳过任何前导的空白字符。
scanf
的返回值
- scanf 函数的返回值是一个整数,表示即成功读取并赋值的变量。如果达到文件结尾或发生输入失败,则返回 EOF(通常是 -1)。
例如,如果你使用 scanf 来读取两个整数,并且输入也正好提供了两个整数,那么 scanf 将返回 2。如果只提供了一个整数,那么 scanf 将返回 1,并留下第二个变量未被赋值。
以下是一个关于 scanf 返回值的简单示例:
#include <stdio.h>
int main() {
int a, b;
int result;
printf("请输入两个整数(用空格分隔): ");
result = scanf("%d %d", &a, &b);
if (result == 2) {
printf("成功读取两个整数: %d 和 %d\n", a, b);
} else if (result == 1) {
printf("只成功读取一个整数: %d\n", a);
printf("第二个整数未被读取。\n");
} else {
printf("输入失败或达到文件结尾。\n");
}
return 0;
}
scanf
尝试读取两个整数。根据 scanf
的返回值,程序会输出不同的消息。
- 如果返回 2,表示两个整数都被成功读取。
- 如果返回 1,表示只成功读取了一个整数,第二个整数没有被读取。
-.如果返回 0 或 EOF(通常是 -1),表示没有读取到任何整数或输入失败。
注意:即使 scanf
遇到了无法匹配的输入(它期望一个整数但得到了一个字符),也可能返回成功读取的项数,并留下未赋值的变量。因此,在使用 scanf
时,应该检查返回值以确保所有期望的变量都被成功赋值。同时,对于字符串输入,还应该注意缓冲区溢出的问题,并考虑使用其他更安全的输入方法,如 fgets
结合 sscanf
。
赋值忽略符
#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d-%d-%d", &year, &month, &day);
printf("%d %d %d\n", year, month, day);
return 0;
}
上述事例中,如果用户输入2020-01-01
,就会正确解读出年、月、日。问题是用户可能输入其他
格式,比如 2020/01/01
,这种情况下, scanf()
解析数据就会失败。
为了避免这种情况, scanf()
提供了⼀个赋值忽略符(assignment suppression character) * 。
只要把 * 加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。
#include <stdio.h>
int main() {
int year, month, day;
char separator;
printf("请输入日期(格式为YYYY-MM-DD): ");
// 使用赋值忽略符来读取并忽略分隔符'-'
scanf("%d-%*c%d-%*c%d", &year, &month, &day);
printf("你输入的日期是: %d-%d-%d\n", year, month, day);
return 0;
}
用户应该输入形如2023-03-31
的日期。%*c
表示读取一个字符(在这里是分隔符’-'),但并不将其赋值给任何变量,而是直接忽略它。这样,即使输入中包含分隔符,scanf
也能正确地读取年、月和日,并将它们分别赋值给year、month
和day
变量。
赋值忽略符在处理复杂的输入或需要跳过某些特定字符时非常有用,它使得scanf
的使用更加灵活和方便。然而,也需要注意,过度依赖scanf
进行输入处理可能会导致代码变得脆弱,不易于理解和维护。对于复杂的输入需求,可能需要考虑使用其他方法。