1、基本语法
printf()
基本用法
printf() 的作⽤是将参数⽂本输出到屏幕。f 代表 format (格式化)
printf() 是在标准库的头⽂件 stdio.h 定义的。使⽤这个函数之前,必须在源码⽂件头部引⼊这个头文件。
#include <stdio.h>
int main(void) {
printf("Hello World\n");
}
2、变量
变量名
变量名属于标识符(identifier)
- 只能由字⺟(包括⼤写和⼩写)、数字和下划线( _ )组成。
- 不能以数字开头。
- ⻓度不能超过63个字符。
- 区分大小写,star,Star,STAR都是不同的变量
变量的声明
每个变量都有⾃⼰的类型(type)
int height, width;
// 等同于
int height;
int width;
变量的赋值
C 语⾔会在变量声明时,就为它分配内存空间,但不会清除内存⾥⾯原来的值。变量会是⼀个随机的值。所以 变量⼀定要赋值以后才能使⽤。
变量的声明和赋值,通常写在一行。
-
赋值表达式有返回值,等于等号右边的值。
int x, y, z, m, n; x = y = z = m = n = 3;
⼀次为多个变量赋值。赋值运算符是从右到左执⾏,所以先为 n 赋值,然后依次 为 m 、 z 、 y 和 x 赋值。
C 语⾔有左值(left value)和右值(right value)的概念。左值是可以放在赋值运算符左边的值,⼀般是变量;右值是可以放在赋值运算符右边的值,⼀般是⼀个具体的值。这是为了强调有些值不能放在赋值运算符的左边,⽐如 x = 1是合法的表达式,但是 1 = x 就会报错。
变量的作用域
作用域(scope)指的是变量⽣效的范围。C 语⾔的变量作⽤域主要有两种:⽂件作⽤域(file scope)和 块作⽤域(block scope)。
**文件作用域(file scope)**是指:在源码⽂件顶层声明的变量,从声明的位置到⽂件结束都有效。
int x = 1;
int main(void)
{
printf("%i\n", x);
}
x在文件顶层声明,从声明位置开始的整个当前⽂件都是它的作⽤域。
**块作用域(block scope)**是指:由⼤括号{ } 组成的代码块,它形成⼀个单独的作⽤域。凡是在块作用域里面声明的变量,只在当前代码块有效。
int a = 12;
if(a == 12){
int b ==99;
printf("%d %d\n",a,b); //输出12 99
}
printf("%d\n",a); //输出12
printf("%d\n",b); //出错
代码块可以嵌套
{
int i = 10;
if(i == 10){
int i = 20;
printf("%d\n",i); //输出20
}
printf("%d\n",i); //输出10
}
注意:因为是“声明”,所以才会这样,正常只是“赋值”
specially :for 的循环条件部分是⼀个单独的作⽤域
//循环条件
for(int i = 0;i < 5;i++)
//循环体内部
{
int i = 999;
printf("%d\n", i); //输出5次999
}
由于循环条件部分是⼀个单独的作⽤域,所以循环体内部的 i 不会修改掉循环变量 i ,因此这段代码的运⾏结果就是打印5次 999 。
3、运算符
算数运算符
- + :正值运算符(⼀元运算符)
- - :负值运算符(⼀元运算符)
- + :加法运算符(⼆元运算符)
- - :减法运算符(⼆元运算符)
- * :乘法运算符
- / :除法运算符
- % :余值运算符
(1)+ - 没什么要说的
⼀元运算符 + 对正负值没有影响,是⼀个完全可以省略的运算符,但是写了也不会报错。
int x = -12;
int y = +x; //y还是-12
(2)* 就乘法
(3)/
Pay attention: 两个整数相除,得到还是⼀个整数。
float x = 6 / 4;
printf("%f\n", x); // 输出 1.000000
C语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分
两个运算数必须⾄少有⼀个浮点数
float x = 6.0 / 4; // 或者写成 6 / 4.0printf("%f\n", x); // 输出 1.500000
another example:
int score = 5;
score = (score / 20) * 100; //只会输出0
5/20=0.25,但都是整数,返回整数部分0
(4) %
表示求模运算,即返回两个整数相除的余值。这个运算符只能⽤于整数,不能用于浮点数。
负数求模的规则是,结果的正负号由第⼀个运算数的正负号决定。
11 % -5 // 1
-11 % -5 // -1
-11 % 5 // -1
(5)赋值运算的简写形式
i += 3; // 等同于 i = i + 3
i -= 8; // 等同于 i = i - 8
i *= 9; // 等同于 i = i * 9
i /= 2; // 等同于 i = i / 2
i %= 5; // 等同于 i = i % 5
自增运算符,自减运算符
- ++
- - -
太简单 不写了
关系运算符
C 语⾔⽤于⽐较的表达式,称为“关系表达式”(relational expression),⾥⾯使⽤的运算符就称为“关系运 算符”(relational operator)
- > ⼤于运算符
- < ⼩于运算符
- >= ⼤于等于运算符
- <= ⼩于等于运算符
- == 相等运算符
- != 不相等运算符
specially:
i < j < k
不会报错,但达不到想要的目的。
i < j 返回值是0或1,最终0或1与 k 进行比较。
应该用:i < j && j < k
逻辑运算符
-
! :否运算符(改变单个表达式的真伪)。
-
&& :与运算符(两侧的表达式都为真,则为真,否则为伪)。
-
|| :或运算符(两侧⾄少有⼀个表达式为真,则为真,否则为伪)。
trait: 逻辑运算符总是先对左侧的表达式求值,再对右边的表达式求值
if (number != 0 && 12/number == 2)
如果 && 左侧的表达式( number != 0 )为伪,即 number 等于 0 时,右侧的表达式 ( 12/number == 2 )是不会执⾏的。因为这时左侧表达式返回 0 ,整个 && 表达式肯定为伪,就直接返回 0 ,不再执行右侧的表达式了。
位运算符
C 语⾔提供⼀些位运算符,⽤来操作⼆进制位(bit)。
(1)取反运算符 ~
取反运算符 ~ 是⼀个⼀元运算符,用来将每⼀个⼆进制位变成相反值,即 0 变成 1 , 1 变成 0 。
// 返回 01101100
~ 10010011
Pay attention: ~ 运算符不会改变变量的值,只是返回⼀个新的值。
(2)与运算符 &
与运算符 & 将两个值的每⼀个⼆进制位进⾏⽐较,返回⼀个新的值。当两个⼆进制位都为 1 时,返回 1 , 否则返回 0 。
// 返回 00010001
10010011 & 00111101
与运算符 & 可以与赋值运算符 = 结合,简写成 &=
i = i & 2;i &= 2;
(3)或运算符 |
或运算符 | 将两个值的每⼀个⼆进制位进⾏⽐较,返回⼀个新的值。两个⼆进制位只要有⼀个为 1 (包含 两个都为 1 的情况),就返回 1 ,否则返回 0 。
// 返回 1011111110010011 | 00111101
或运算符 | 可以与赋值运算符 = 结合,简写成 |=
(4)异或运算符 ^
异或运算符 ^ 将两个值的每⼀个⼆进制位进⾏⽐较,返回⼀个新的值。两个⼆进制位有且仅有⼀个为 1时 , 就返回 1 ,否则返回 0 。
// 返回 10101110 10010011 ^ 00111101
异或运算符^ 可以和赋值运算符 = 结合,简写为^=
(5)左移运算符 <<
左移运算符<<讲左侧运算数的每一位,向左移动指定的位数,尾部空出来的位置使⽤ 0 填充。
// 1000101000
10001010 << 2
左移运算符 << 可以与赋值运算符 = 结合,简写成 <<=
(6)右移运算符 >>
右移运算符 >> 将左侧运算数的每⼀位,向右移动指定的位数,尾部⽆法容纳的值将丢弃,头部空出来的位 置使⽤ 0 填充。
// 返回 00100010
10001010 >> 2
Pay attention: 右移运算符最好只⽤于⽆符号整数,不要⽤于负数。
右移运算符 >> 可以与赋值运算符 = 结合,简写成 >>=
逗号运算符
逗号运算符返回最后⼀个表达式的值,作为整个语句的值。
int x;x = 1, 2, 3;
逗号的优先级低于赋值运算符,所以先执行赋值运算,再执行逗号运算,变量 x 等于 1 。
运算优先级
部分运算符的优先级顺序(按照优先级从⾼到低排列)
- 圆括号( () )
- ⾃增运算符( ++ ),⾃减运算符( – )
- ⼀元运算符( + 和 - )
- 乘法( * ),除法( / )
- 加法( + ),减法( - )
- 关系运算符( < 、 > 等)
- 赋值运算符( = )
完全记住所有运算符的优先级没有必要,解决⽅法是多⽤圆括号,防⽌出现意料之外的情况,也有利于提⾼ 代码的可读性。
4、流程控制
条件执⾏和循环执⾏
if语句
dddd
三元运算符 ?:
if…else的简写形式
?:
表达式expression1为ture(非0值)时,就执行expression2,否则就执行expression3
examole:
(i > j)? i : j
返回两个值之中的较大值,等同于:
if (i > j)
return i;
else
return j;
switch语句
dddd
while 语句
do…while 结构
for 语句
break语句
两种用法:1.与switch语句配套使用,用来中断某个分支的执行。
2.在循环体内部跳出循环,不再进行后面的循环。
while ((ch = getchar()) != EOF) {
if (ch == '\n') break;
putchar(ch);
}
读取到换行符(\n),break命令就跳出整个while循环,不在读取了
Pay attention: break命令只能跳出switch结构,不能跳出if结构。
continue 语句
用于在循环体内部终止本轮循环,进入下一论循环。
while ((ch = getchar()) != EOF) {
if (ch == '\t') continue;
putchar(ch);
}
读取到换行符(\t),就用continue语句跳过该字符,读取下一字符。
goto 语句
goto 语句⽤于跳到指定的标签名
char ch;
top: ch = getchar();
if (ch == 'q')
goto top;
top
就是一个标签名,可以放在正常语句前面,相当于做了一个标记。
goto的一个主要用法就是跳出多层循环
for(...) {
for (...) {
while (...) {
do {
if (some_error_condition)
goto bail;
} while(...);
}
}
}
bail:
// ... ...
不用goto的话,想跳出所有循环,很麻烦。
goto的另一个用途是 提早结束多重判断
if (do_something() == ERR)
goto error;
if (do_something2() == ERR)
goto error;
if (do_something3() == ERR)
goto error;
if (do_something4() == ERR)
goto error;
上面四个判断,发现一个错误,就能用goto跳过后面的判断。
Pay attentiion: goto只能在同一个函数之中跳转,并不能跳转到其他函数。
5、数据结构
基本的数据类型有三个:字符(char)、整数(int)、浮点数(float)。
字符类型
C 语言规定,字符常量必须放在单引号里面。
在计算机内部,字符类型使用一个字节(8位)存储。c语言将其当作整数处理。
只要在字符类型的范围之内,整数与字符是可以互换的。(0 到 127 的 ASCII 字符范围)。
char c = 66;
//等同于
char c = 'B';
单引号本身也是一个字符,如果要表示这个字符常量,要使用反斜杠转义。
char t = '\'';
- \a :警报,这会使得终端发出警报声或出现闪烁,或者两者同时发⽣。
- \b :退格键,光标回退⼀个字符,但不删除字符。
- \f :换页符,光标移到下⼀⻚。在现代系统上,这已经反映不出来了,行为改成类似于 \v 。
- \n :换行符。
- \r :回车符,光标移到同⼀⾏的开头。
- \t :制表符,光标移到下⼀个⽔平制表位,通常是下⼀个8的倍数。
- \v :垂直分隔符,光标移到下⼀个垂直制表位,通常是下⼀⾏的同⼀列。
- \0 :null 字符,代表没有内容。注意,这个值不等于数字0。
转义写法还有使用八进制和十六进制表示一个字符。
- \nn : 字符的⼋进制写法, nn 为⼋进制值。
- \xnn :字符的⼗六进制写法, nn 为⼗六进制值。
char x = 'B';
char x = 66;
char x = '\102'; // ⼋进制
char x = '\x42'; // ⼗六进制
整数类型
int
表示较大的数,常见是使用4个字节(32位)存储一个int类型。
- 16位:-32,768 到 32,767。
- 32位:-2,147,483,648 到 2,147,483,647。
- 64位:-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。
signed,unsigned
int 等同于 signed int
16位:
- signed int 范围:-32,768 到 32,767
- unsigned int 范围: 0 到 65,535
字符类型 char 也可以设置 signed 和 unsigned 。
signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为 0 到 255
整数的子类型
int 太大了,对于小整数浪费空间。对于太大的,还会不够用。
- short int (简写为short):占用空间不多于int,一般占用两个字节(整数范围为-32768~32767)。
- long int (简写为long) : 占用空间不少于int,至少为4个字节。
- long long int (简写为 long long ):占⽤空间多于 long ,⾄少为8个字节。
C 语⾔允许省略 int
整数类型的极限值
有时候需要查看,当前系统不同整数类型的最⼤值和最⼩值,C 语⾔的头⽂件 limits.h 提供了相应的常量
- SCHAR_MIN , SCHAR_MAX :signed char 的最⼩值和最⼤值。
- SHRT_MIN , SHRT_MAX :short 的最⼩值和最⼤值。
- INT_MIN , INT_MAX :int 的最⼩值和最⼤值。
- LONG_MIN , LONG_MAX :long 的最⼩值和最⼤值。
- LLONG_MIN , LLONG_MAX :long long 的最⼩值和最⼤值。
- UCHAR_MAX :unsigned char 的最⼤值。
- USHRT_MAX :unsigned short 的最⼤值。
- UINT_MAX :unsigned int 的最⼤值。
- ULONG_MAX :unsigned long 的最⼤值。
- ULLONG_MAX :unsigned long long 的最⼤值。
整数的进制
c语言的整数默认都是十进制
八进制用0做前缀
int a = 012; //八进制,相当于十进制的10
十六进制用0x
或0X
做前缀
int a = 0x1A2B; //十六进制,进制相当于十进制的6699
有些编译器使⽤ 0b 前缀,表示⼆进制数,但不是标准。
int x = 0b101010;
printf() 的进制相关占位符如下。
- %d :⼗进制整数。
- %o :⼋进制整数。
- %x :⼗六进制整数。
- %#o :显示前缀 0 的⼋进制整数。
- %#x :显示前缀 0x 的⼗六进制整数。
- %#X :显示前缀 0X 的⼗六进制整数。
输出格式
(1)限定宽度
printf("%5d\n",123); //右对齐,输出" 123"
printf("%-5d\n",123); //左对齐,输出"123 "
对于小数,这个限定符会限制所有数字的最小显示宽度
// 输出 " 123.450000"
printf("%12f\n", 123.45);
%12f
表示输出的浮点数最少要占据12位,因为小数的默认显示精度是小数点后6位,所以在123.45的头部添加了两个空格。
(2)总是显示正负号
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
不改变正负。
(3)限定小数位数
希望给小数点后只保留两位,占位符可以写成 %.2f
// 输出 Number is 0.50
printf("Number is %.2f\n", 0.5);
同时限定宽度
// 输出 Number is " 0.50"
printf("Number is %4.2f\n",0.5);
//等同于
printf("Number is %*.*f\n",4,2,0.5);
(4)输出部分字符串
%s
占位符用来表示输出的字符串,默认全部输出。如果主要开头的部分,可以用%.[m]s
指定输出的长度
// 输出 hello
printf("%.5s\n","hello world");
浮点数类型
浮点数的类型声明使⽤ float 关键字,可以⽤来声明浮点数变量。
m * be 的形式,存储⼀个数值, m 是小数部分, b 是基数(通常是 2 ), e 是指数部分
float 类型占⽤4个字节(32位),其中8位存放指数的值和符号,剩下24位存放⼩数的值和符号。 float 类型⾄少能够提供(⼗进制的)6位有效数字,指数部分的范围为(⼗进制的) -37 到 37 ,即数值范围为 10-37到1037
两个更大的浮点数类型:
double
:占用8个字节(64位),至少提供13位有效数字。long doube
: 通常占用16个字节。
Pay attention:由于存在精度限制,浮点数只是一个近似值,计算是不精确的
if (0.1 + 0.2 == 0.3) // false
可以用科学计数法
double x = 123.465e+3; //123.456 x 10^3
//等同于
double x = 123.456e3;
布尔类型
使用0表示伪,所有非零值表示真。
C99标准里添加的类型_Bool
表示布尔值
_Bool number;
number = 1;
if(number)
{ printf("is ture.\n"); }
头文件stdbool
定义了另一个类型别名bool
并且定义了 true 代表 1 、 false 代表 0 。只要加载这 个头⽂件,就可以使⽤这⼏个关键字。
#include <stdbool.h>
bool flag = false;
字面量的值
字面量(literal)指代码里直接出现的值
int = 123;
123就是字面量。编译时,字面量也会写入内存,所以编译器必须为字面量指定数据类型(就像必须为变量指定数据类型⼀ 样)。
一般情况下十进制的整数123会被编译器认定为int
型,如果超过了int
的范围,就认定为long int
,再超就unsigned long
字面量后缀(有什么用?)
f 和 F : float 类型。
l 和 L :对于整数是 long int 类型,对于⼩数是 long double 类型。
ll 和 LL :Long Long 类型,⽐如 3LL 。
u 和 U :表示 unsigned int ,⽐如 15U 、 0377U 。
溢出
每一个数据类型都有数值范围,超出之后,需要更多的二进制位存储,向上溢出(overflow),向下溢出(underflow)
unsigned char x = 255;
x += x;
printf("%d\n",x); //输出0
运算时如果为了避免溢出,有个方法就是将运算结果与类型的极限值进行比较。
unsigned int a,b,sum;
//正确
if(a > UINI_MAX - B) too_big();
else sum = a + b;if(a > UINI_MAX - B) too_big();
else sum = a + b;
//错误 因为a + b 的结果本身就是溢出值,小于UINI_MAX
if(a + B> UINI_MAX) too_big();
else sum = a + b;
siezof运算符
返回某个数据类型或某个值占用的字节数量。它的参数可以是数据 类型的关键字,也可以是变量名或某个具体的值。
sizeof 运算符的返回值,C 语⾔只规定是⽆符号整数,并没有规定具体的类型,⽽是留给系统⾃⼰去决 定, sizeof 到底返回什么类型。不同的系统中,返回值的类型有可能是 unsigned int ,也有可能 是 unsigned long ,甚⾄是 unsigned long long ,对应的 printf() 占位符分别是 %u 、 %lu 和 %llu 。 这样不利于程序的可移植性。
C 语⾔提供了⼀个解决⽅法,创造了⼀个类型别名 size_t ,⽤来统⼀表示 sizeof 的返回值类型。该别名 定义在 stddef.h 头⽂件(引⼊ stdio.h 时会⾃动引⼊)⾥⾯,对应当前系统的 sizeof 的返回值类型,可 能是 unsigned int ,也可能是 unsigned long 。
C 语⾔还提供了⼀个常量 SIZE_MAX ,表示 size_t 可以表示的最⼤整数。所以, size_t 能够表示的整数 范围为 [0, SIZE_MAX] 。
printf() 有专⻔的占位符 %zd 或 %zu ,⽤来处理 size_t 类型的值。
类型的自动转换
赋值运算
(1)浮点数值赋予整数变量
c语言直接舍弃小数部分
int x = 3.14; //x = 3
直接舍弃,不是四舍五入。
(2)整数赋值给浮点数变量
自动转换为浮点数
float x = 4; //x = 4.0
(3)窄类型赋值给宽类型
字节较小的整数类型,赋值给字节宽度较大的整数类型时,就是窄变宽
char x = 10;
int i = x + 10;
变量 x 的类型是 char ,由于赋值给 int 类型,所以会⾃动提升为 int 。
(4)宽类型赋值给窄类型
会发生类型降级,自动转换为后者。这时可能会 发⽣截值(truncation),系统会⾃动截去多余的⼆进制位,导致难以预料的结果。
int i =321;
char ch = i; //ch的值位65 (321-255)
浮点数赋值给整数类型的值,也会发⽣截值,浮点数的⼩数部分会被截去。
double pi = 3.14159;
int i = pi; // i 的值为 3
混合类型的运算
小的转大的,窄的转宽的
最好避免无符号整数与有符号整数的混合运算。因为这时 C 语⾔会⾃动将 signed int 转 为 unsigned int ,可能不会得到预期的结果。
整数类型的运算*
两个相同类型的整数运算时,或者单个整数运算时,一般来说运算结果也是同一类型。
但有一个例外,宽度小于int
类型,运算结果会自动提升为int
的类型
unsigned char a = 66;
if((-a) < 0)
printf("negative\n");
else
printf("positive\n");
变量a时unsigned char
类型,不能小于0,但(-a)会自动转换为int类型,导致上面的代码输入negative。
函数
函数的参数和返回值,会自动转换为函数定义里指定的值
int example(int a, unsigned char b)
char m = 42;
unsigned short n = 43;
long long int c = example(m,n)
//m n的类型,传入后 会变成example里定义的参数类型
char example(void)
{
int a = 42;
return a; //返回的还是char 因为函数定义的char
}
类型的显式转换
手动转换类型的方法
在一个值或者变量前面,使用圆括号指定类型(type),就可以使这个之或者变量转为指定的值。叫做“类型指定(casting)”。
(unsigned char) ch
ch 转成⽆符号的字符类型
long int y = (long int) x + 10
(long int) 将 x 显式转为 long int 类型
可移植类型**
在声明时,准确控制变量的字节宽度,头⽂件 stdint.h 创造了⼀些新 的类型别名。
(1)精确宽度类型(exact-width integer type),保证某个整数类型的宽度是确定的。
int8_t :8位有符号整数。
int16_t :16位有符号整数。
int32_t :32位有符号整数。
int64_t :64位有符号整数。
uint8_t :8位⽆符号整数。
uint16_t :16位⽆符号整数。
uint32_t :32位⽆符号整数。
uint64_t :64位⽆符号整数。
(2)最小宽度类型(minimum width type),保证某个整数类型的最小长度。
int_least8_t
int_least16_t
int_least32_t
int_least64_t
uint_least8_t
uint_least16_t
uint_least32_t
uint_least64_t
可以保证占据的字节不少于指定宽度
(3)最快的最⼩宽度类型(fast minimum width type),可以使整数计算达到最快的类型。
int_fast8_t
int_fast16_t
int_fast32_t
int_fast64_t
uint_fast8_t
uint_fast16_t
uint_fast32_t
uint_fast64_t
上⾯这些类型是保证字节宽度的同时,追求最快的运算速度
⽐如 int_fast8_t 表示对于8位有符号整数, 运算速度最快的类型。这是因为某些机器对于特定宽度的数据,运算速度最快
(4)可以保存指针的整数类型。
intptr_t :可以存储指针(内存地址)的有符号整数类型。
uintptr_t :可以存储指针的⽆符号整数类型。
(5)最⼤宽度整数类型,⽤于存放最⼤的整数。
intmax_t :可以存储任何有效的有符号整数的类型。
uintmax_t :可以存放任何有效的⽆符号整数的类型。
上⾯的这两个类型的宽度⽐ long long 和 unsigned long 更⼤。