目录
1. 数据类型介绍
C语⾔提供了丰富的数据类型来描述⽣活中的各种数据。使⽤整型类型来描述整数,使⽤字符类型来描述字符,使⽤浮点型类型来描述⼩数。
数据类型:相似的数据所拥有的共同特征。
// 1. 字符型 -- 长度为 1 字节
char
[signed] char //有符号的
unsigned char //⽆符号的
// 2. 整型
// 2.1 短整型 -- 长度为 2 字节
short [int]
[signed] short [int]
unsigned short [int]
// 2.2 整型 -- 长度为 4 字节
int
[signed] int
unsigned int
// 2.3 ⻓整型 -- 长度为 4 字节
// C语言规定 long 的长度 >= int,对于不同的编译器长度不同,vs 使用的编译器为 4 字节。
long [int]
[signed] long [int]
unsigned long [int]
// 2.4 更长的整型 -- 长度为 8 字节
// C99 中引⼊
long long [int]
[signed] long long [int]
unsigned long long [int]
// 3. 浮点型
float -- 长度为 4 字节
double -- 长度为 8 字节
long double -- 规定 long double 的长度 >= double,不同编译器长度不同,vs 下为 8 字节 // C99 引入
// 4. 布尔类型 -- 长度为 1 字节
// C 语⾔原来并没有为布尔值单独设置⼀个类型,⽽是使⽤整数 0 表⽰假,⾮零值表⽰真。
// 在 C99 中也引⼊了 布尔类型 ,是专⻔表⽰真假的。布尔类型的使⽤得包含头⽂件 <stdbool.h>。
// 布尔类型变量的取值是: true 或者 false .
_Bool -- 长度为 1 字节
1.1 各种数据类型的长度
每⼀种数据类型都有⾃⼰的⻓度,使⽤不同的数据类型,能够创建出⻓度不同的变量,变量⻓度的不同,存储的数据范围就有所差异。
1.1.1 sizeof 操作符
sizeof 是⼀个关键字,也是操作符,sizeof 用于计算类型、表达式以及变量的长度,单位是字节。
sizeof( 类型 )
sizeof 表达式
(1)sizeof 的操作数是表达式或者变量的时候,可以省略掉后边的括号。sizeof 的操作数是类型时必须加上 "()" 。
(2)sizeof 后面的表达式不真实参与运算,只是根据表达式的类型得出大小。
(3)sizeof 的计算结果类型为 size_t 类型。C 语言规定 sizeof 运算符的返回值是无符号整数,并没有规定具体的类型,而是留给系统自己去决定。不同的系统中,返回值的类型不同,有可能是 unsigned int,也可能是 unsigned long,甚至是 unsigned long long,打印的时候占位符分别是 %u、%lu 和 %llu,这样不利于移植。C语言创造一个类型别名为 size_t 的类型,这个类型会根据系统的变化而变化,统一使用 %zd 占位符。
1.1.2 数据类型长度
#include <stdio.h>
int main()
{
printf("_Bool的长度:%zd 字节\n", sizeof(_Bool));
printf("char的长度:%zd 字节\n", sizeof(char));
printf("short的长度:%zd 字节\n", sizeof(short));
printf("int的长度:%zd 字节\n", sizeof(int));
printf("long的长度:%zd 字节\n", sizeof(long));
printf("long long的长度:%zd 字节\n", sizeof(long long));
printf("float的长度:%zd 字节\n", sizeof(float));
printf("double的长度:%zd 字节\n", sizeof(double));
printf("long double的长度:%zd 字节\n", sizeof(long double));
return 0;
}
1.1.3 sizeof 中表达式不计算
#include <stdio.h>
int main()
{
short s = 5;
int b = 10;
printf("%zd\n", sizeof(s = b + 1)); // sizeof 内的表达式不计算
printf("s = %d\n", s);
return 0;
return 0;
}
sizeof 在代码进行编译的时候就根据表达式的类型确定了,所以在允许期间就不会执行表达式了。
2. signed 和 unsigned
C语言使用 signed 和 unsigned 关键字修饰字符型和整型。
signed 关键字,表⽰⼀个类型带有正负号,包含负值;
unsigned 关键字,表⽰该类型不带有正负号,只能表⽰零和正整数。
对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int 。 char 类型默认是否带有正负号由编译器决定,它有可能是 signed char ,也有可能是 unsigned char。
整数变量声明为 unsigned 的好处是同样⻓度的内存能够表⽰的最⼤整数值,增⼤了⼀倍。16位的 signed short int 的取值范围是:-32768~32767,最⼤是32767;⽽ unsigned short int 的取值范围是:0~65535,最⼤值增⼤到了65535。
limits.h 文件中说明了字符型和整型的取值范围。float.h 中说明了浮点数类型的取值范围。
unsigned int 中的 int 可以忽略,可以使用下列进行变量的声明。
unsigned a;
3. 变量
3.1 变量的创建
类型是用来创建变量的,C语言中把经常变化的值称为变量,不变的值称为常量。
unsigned int age; // age 是一个 unsigned int 类型的变量
char ch; // ch 是一个 char 的变量
变量在创建的时候就给一个初始值就叫做变量的初始化。
int age = 18;
char ch = 'w';
double weight = 48.0;
unsigned int height = 100;
3.2 变量的分类
(1)全局变量:在大括号外部定义的变量就是全局变量。全局变量的使用范围广,整个工程中想使用都是有办法使用的。
(2)局部变量:在大括号内部定义的变量就是局部变量。局部变量的使用范围比较局限,只能在自己所在的局部范围内进行使用。
#include <stdio.h>
int global = 2025;//全局变量
int main()
{
int local = 2018;//局部变量
printf("local: %d\n", local);
printf("global: %d\n", global);
return 0;
}
当全局变量和局部变量名相同的时候,采用就近原则进行使用。
#include <stdio.h>
int n = 100;
int main()
{
{
int n = 10;
printf("%d\n", n); // 局部变量
}
printf("%d\n", n); // 全局变量
return 0;
}
知识点1:
一个全局变量不初始化,默认值为 0,一个局部变量不初始化,默认值是随机值。所以在创建变量的时候,尽量进行初始化,避免出现随机值的干扰。
在 C/C++ 中,一般会关注内存中的三个区域:栈区、堆区、静态区。
局部变量存放在栈区;全局变量存放在静态区,堆区又来动态内存管理(暂不介绍)。
4. 算数操作符
C语⾔中为了⽅便运算,提供了⼀系列操作符,也叫做运算符,其中有⼀组操作符叫:算术操作符,分别是: + - * / % ,这些操作符都是双目操作符。
#include <stdio.h>
int main()
{
int x = 4 + 16;
int y = 60 - 20;
int num = 5;
printf("%d\n", x);
printf("%d\n", y);
printf("%d\n", num * num);
return 0;
}
其中的 + - * 和数学上是一样的。+ - * 叫做操作符,符号两边的数字叫做操作数,这种有两个操作数的操作符叫做双目操作符。
#include <stdio.h>
int main()
{
int x = 6;
int y = 4;
float f = x / y;
int i = x / y;
printf("%f\n", f);
printf("%d\n", i);
return 0;
}
如上所示,在数学中 6 / 4 应该为 1.5,但是上述的输出为什么都是 1 呢?因为在 C语言中, 如果 / 操作符的两个操作数都是整型,则结果只会返回整数部分,如果 / 操作符的两个操作数有一个为浮点型,结果返回浮点数。
#include <stdio.h>
int main()
{
float x = 6;
int y = 4;
float f = x / y;
int i = x / y;
printf("%f\n", f);
printf("%d\n", i);
return 0;
}
将上述的 x 改为 float 类型,第一个结果是 1.500000,第二个结果是 1。 原因是这时的 x / y 得到的结果类型为 float,然后float 类型的变量赋值给 int 类型,会自动截断小数部分。
常量进行运算的时候,比如写成 20 时,这时候为整数,写成 20.0 时,这时候为浮点数。
运算符 % 表示求模运算,即返回两个整数相除的余数。该运算符只能用于整数。
#include <stdio.h>
int main()
{
int res = 6 % 4;
printf("%d\n", res);
return 0;
}
负数求模的规则是,结果的正负号由第一个操作数的正负号决定。
#include <stdio.h>
int main()
{
printf("%d\n", 6 % 4);
printf("%d\n", 6 % -4);
printf("%d\n", -6 % 4);
printf("%d\n", -6 % -4);
return 0;
}
5. 赋值操作符
5.1 赋值运算符 =
在变量创建的时候给一个初始值叫初始化,在变量创建好后再给一个值,叫做赋值。其中 " = " 叫做赋值运算符。
int a = 100;//初始化
a = 200;//赋值,这⾥使⽤的就是赋值操作符
赋值操作符也可以连续赋值:
int a = 3;
int b = 5;
int c = 0;
c = b = a+3; //连续赋值,从右向左依次赋值的。这时 c 和 b 的值都为 a + 3
5.2 复合赋值运算符
+= -=
*= /= %=
//下⾯的操作符后续介绍
>>= <<=
&= |= ^=
当对一个数进行自增、自减等操作时,可以使用上述复合赋值符。
int a = 5;
a = a + 5; 等同于 a += 5;
6. 单目操作符
C语⾔中只有⼀个操作数的操作符被称为单⽬操作符。 ++、--、+(正)、-(负) 就是单⽬操作符的。
6.1 前置 ++/-- 和后置 ++/--
++/-- 是一种自增/自减的操作符。
当使用前置++时(前置--同理),操作数是先进行自增之后,然后再带入表达式中进行运算。也就是:先 +1,后使用。
#include <stdio.h>
int main()
{
int a = 10;
int b = ++a;//++的操作数是a,是放在a的前⾯的,就是前置++
printf("a=%d b=%d\n", a, b);
return 0;
}
a 原来是 10,先 +1 后 a 变成了 11,再使⽤就是赋值给 b,b 得到的也是 11,所以计算后 a 和 b 都是 11,相当于下列代码:
int a = 10;
a = a+1;
b = a;
printf("a=%d b=%d\n",a , b);
后置++时(后置--同理),操作数先进行表示式中的运算,然后再进行自增操作。 也就是:先使用,后 +1。
#include <stdio.h>
int main()
{
int a = 10;
int b = a++;//++的操作数是a,是放在a的前⾯的,就是前置++
printf("a=%d b=%d\n", a, b);
return 0;
}
6.2 + 和 -
这里的 + 是正号,- 是负号,都是单目操作符。运算符 + 对正负值没有影响,是一个完全可以省略的运算符,写了也不报错。
int a = +10; 等价于 int a = 10;
运算符 - 用来改变一个值的正负号。
#include <stdio.h>
int main()
{
int a = 10;
int b = -a;
int c = -10;
printf("b=%d c=%d\n", b, c);//这⾥的b和c都是-10
}
7. 强制类型转换
在操作符中还有一种特殊的操作符叫做强制类型转换,语法形式很简单,形式如下:
(类型)操作数
#include <stdio.h>
int main()
{
int a = 3.14;
printf("%d\n", a);
return 0;
}
a 的类型是 int 类型,3.14 是 double 类型,两边的类型不一致,强行赋值编译器会出现报警。但是可以使用强制类型转换来将 3.14 转换成 int 类型,这种强制类型转换只取整数部分。
#include <stdio.h>
int main()
{
int a = (int)3.14;
printf("%d\n", a);
}
8. scanf 和 printf 介绍
8.1 printf
printf 函数需要包含头文件 stdio.h。
printf() 的作⽤是将参数⽂本输出到屏幕。名字里面的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。
printf() 不会在行尾自动添加换行符,运⾏结束后,光标就停留在输出结束的地⽅,不会⾃动换⾏。
8.1.1 占位符
printf() 可以在输出⽂本中指定占位符。所谓 “占位符” 就是这个位置可以⽤其他值代入。
#include <stdio.h>
int main()
{
printf("There are %d apples\n", 3);
return 0;
}
上⾯⽰例中, There are %d apples\n 是输出⽂本,里面的 %d 就是占位符,表示这个位置要⽤其他值来替换。占位符的第⼀个字符⼀律为百分号 % ,第⼆个字符表⽰占位符的类型, %d 表⽰这⾥代⼊的值必须是⼀个整数。
输出文本⾥⾯可以使⽤多个占位符。
int main()
{
printf("%s says it is %d o'clock\n", "lisi", 21);
return 0;
}
上⾯⽰例中,第⼀个是字符串占位符 %s ,第⼆个是整数占位符 %d ,分别对应 printf() 的第⼆个参数( lisi )和第三个参数( 21 )。执⾏后的输出就是 lisi says it is 21 o'clock 。
printf() 参数与占位符是⼀⼀对应关系,如果有 n 个占位符, printf() 的参数就应该有 n +
1 个。如果参数个数少于对应的占位符, printf() 可能会输出内存中的任意值。
8.1.2 占位符表
占位符 | 对应输出的数据类型 |
---|---|
%c | char 类型字符 |
%d | 十进制 int 类型 |
%f | 小数(包含 float 和 double 类型) |
%ld | 十进制 long int 类型 |
%lu | unsigned long int 类型 |
%lf | double 类型 |
%Lf | long double 类型 |
%p | 指针(地址) |
%s | 字符串 |
%u | unsigned int 类型 |
%x | 十六进制整数 |
%zd | size_t 类型 |
8.1.3 printf 输出格式
8.1.3.1 限制宽度
printf() 允许限定占位符的最小宽度。
#include <stdio.h>
int main()
{
printf("%d\n", 123);
printf("%5d\n", 123); // 输出占 5 位,右对齐
return 0;
}
如果限制了占位符的最小宽度,输出的值默认是右对齐。 如果希望改成左对齐,可以在占位符的 % 后面插入一个 - 号。
#include <stdio.h>
int main()
{
printf("%d\n", 123);
printf("%5d\n", 123); // 输出占 5 位
printf("%-5dxx\n", 123);
return 0;
}
这个限定符也会限制小数打印的宽度,小数默认打印小数部分前六位。
#include <stdio.h>
int main()
{
printf("%12f\n", 123.45);
return 0;
}
8.1.3.2 总显示正负号
默认情况下, printf() 不对正数显⽰ + 号,只对负数显⽰ - 号。如果想让正数也输出 + 号,可以在占位符的 % 后⾯加⼀个 + 。添加之后如果打印的是负数,而没有任何影响。
#include <stdio.h>
int main()
{
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
return 0;
}
8.1.3.3 限定小数位数
输出小数时,有时希望限定小数的位数,则可以在 % 后面 加上 .x,表示显示 x 位小数。
#include <stdio.h>
int main()
{
printf("1. Number is %f\n", 0.5);
printf("2. Number is %.2f\n", 0.5);
return 0;
}
这种写法可以与限定宽度占位符结合使用。
#include <stdio.h>
int main()
{
printf("%6.2f\n", 0.5); // 最少显示 6 位,小数显示 2 位
return 0;
}
最小宽度和小数点位数这两个限定值,可以用 * 代替,通过 printf 的参数传入。
#include <stdio.h>
int main()
{
printf("%*.*f\n", 6, 2, 0.5);
return 0;
}
8.1.3.4 输出部分字符串
%s 占位符用来输出字符串,默认是全部输出。如果只想输出开头的部分,可以使用 %.[m]s 指定输出的长度,其中 m 表示输出的长度。
#include <stdio.h>
int main()
{
printf("%.5s\n", "hello world");
return 0;
}
8.2 scanf
8.2.1 scanf 的基本介绍
给变量输入值可以使用 scanf 函数,如果需要将变量的值输出在屏幕上的时候可以使用 printf 函数。
scanf() 用于读取用户的键盘输入,程序允许到这个语句的时候会停下来等待用户从键盘输入。用户输入数据并按下回车后,scanf() 才会处理用户的输入,将其存入变量。
scanf() 函数在使用的时候也需要包含头文件 <stdio.h>。
scanf() 第一个参数是一个格式字符串,里面会放置占位符,告诉编译器如何解读用户的输入,需要提取的数据是什么类型。它的其余参数就是存放用户输入的变量的地址。
#include <stdio.h>
int main()
{
int score = 0;
printf("请输入成绩:");
scanf("%d", &score); // & 是取地址运算符
printf("成绩是:%d\n", score);
return 0;
}
scanf() 处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符等。
scanf() 处理用户的输入的原理是,用户的输入先放入缓存中,等待按下回车键后,按照占位符对缓冲进行解读。解读用户输入时,会从上一个遗留的第一个字符开始,直到读完缓冲或者遇到第一个不符号条件的字符为止。
#include <stdio.h>
int main()
{
int i = 0, j = 0;
float x = 0.0f, y = 0.0f;
scanf("%d%d%f%f", &i, &j, &x, &y); // 占位符是粘在一起的,在输入的时候数据之间使用空格隔开,这样在解读的时候就可以分开进行解读。
printf("i = %d\n", i);
printf("j = %d\n", j);
printf("x = %f\n", x);
printf("y = %f\n", y);
}
%c 占位符,不会自动忽略起始的空白字符。 %c 作为占位符是,总是返回当前要解析位置的第一个字符,无论该字符是否为空格。
#include <stdio.h>
int main()
{
char c = '0';
scanf("%c", &c);
printf("%cxxxx", c);
return 0;
}
如上图,当输入 " abc" 时,这时候 %c 读取到的是第一个空白字符(空格)。
如上图,连续输入两个换行符(回车),%c 读取到第一个空白字符(换行符),第二个换行符是通知 scanf 对读到的数据进行解析,通过 printf 可以看到,打印的结果是一个空行和 xxxx,而这个空行就是 %c 占位符读到的换行符。
在占位符 %c 前面加上一个空格,这样就可以使 %c 占位符忽略所有空格和换行符。
占位符 %s 从当前第一个非空白字符开始读,直到遇到空白字符(空格、换行符顿号制表符)为止。scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符 \0。
#include <stdio.h>
int main()
{
char arr[10] = { 0 };
scanf("%s", arr);
printf("%s", arr);
return 0;
}
8.2.2 scanf 的返回值
scanf() 的返回值是一个整数,表示成功读取的变量个数。如果没有读取任何项或者匹配失败,则返回 0。如果在成功读取任何数据之前,发生了读取错误或遇到读取到文件结尾,则返回常量 EOF = -1(End-Of-File)。
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
int c = 0;
int ret = scanf("%d %d %d", &a, &b, &c);
printf("r = %d\n", ret);
printf("a=%d b=%d c=%d\n", a, b, c);
return 0;
}
如上图,是 scanf 正常读取的情况,返回值为读取到的变量个数。
如上图,当输入两个数字,再输入 Ctrl + z 然后回车表示提前结束,这个时候也是返回读取到的变量个数。
如上图,匹配失败没有读取当任何数据的时候,返回 0。
如上图,在 vs 环境中按 3 次连续输入三个 ctrl + z 和回车,这时候结束程序表示读取出错,返回 EOF。
可以利用 scanf 返回值的特性来进行循环读取数据进行处理。
#include <stdio.h>
int main()
{
int x = 0, y = 0;
while (scanf("%d %d", &x, &y) == 2)
{
printf("输入的两个值相加的结果为:%d\n", x + y);
}
return 0;
}
8.2.3 scanf 的安全性
scanf() 是一个不安全的函数,就以下列这个例子来说明:
#include <stdio.h>
int main()
{
char arr[6] = { 0 };
scanf("%s", arr); // 数组名就是数组的地址,所以这里不需要 &
printf("%s", arr);
return 0;
}
上述代码中,通过 scanf 向一个数组输入字符,当输入的字符大小没有超过数组大小时,执行的结果是正常的。
当输入的字符数量大于数组大小的时候,字符会被正常写入,但是程序会崩溃。原因是,scanf 不管给的变量大小是多大,都会先将输入的数据写入变量中,当输入的数据长度超过数组大小时,会导致内存越界访问,进而导致程序崩溃。
为了防止这种情况,可以将占位符 %s 写成 %[m]s,表示最长只能读取 m 个字符。
8.2.4 赋值忽略符
有时候输入的格式和 scanf 第一个参数格式化字符串的格式不同,就会导致匹配失败的情况。
#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;
}
如上图,正常在输入的时候按照 scanf 的格式进行输入是不会出错的。
如上图,当 scanf 中有格式控制的时候,空白字符不会被忽略,这时候不按照 scanf 的格式输入时就会出错。
scanf() 提供了一个赋值忽略符 *。只要把 * 加在任何占位符的百分号后面,该占位符就不会返回值,解析后将被丢弃。
#include <stdio.h>
int main()
{
int year = 0;
int month = 0;
int day = 0;
scanf("%d%*c%d%*c%d", &year, &month, &day);
printf("%d %d %d\n", year, month, day);
return 0;
}
如上所示,%*c 这个占位符没有对应的变量,解析后被丢弃。