类型限定符使用于变量进行修饰限定的。类型限定符有四个,如下表所示:
限定符 | 含义 |
---|---|
extern | 声明一个变量,extern声明的变量没有建立存储空间,也不能提升为变量定义。 |
const | 定义一个变量,该变量为只读变量,变量的值不能被直接修改。 |
volatile | 防止编译器优化代码 |
register | 定义寄存器变量。寄存器变量位于CPU中,而不是在内存之中,所以没有内存地址,不能使用&进行取地址操作。定义寄存器变量可以提高效率。但是使用register不一定会成功定义一个寄存器变量,如果CPU存在空闲的寄存器,则定义寄存器变量生效,否则仍存在于内存之中,register修饰符无效。 |
在C语言中,字符串是用双引号引起来的字符序列,且以字符’\0’结尾。输出字符串通常使用printf()
函数,该函数的使用方法如下:
printf(格式字符串[, 参数1,参数2, ...]);
printf()
中的格式字符串有多少个格式字符,那么后面就需要跟多少个参数进行对应的填充。对于格式字符,在前面Day02的内容已经对大部分进行了总结。下面继续补充一些:
格式字符 | 含义 |
---|---|
%p | 地址 |
%m.n | 输出实数用到,一共有m位,小数占n位 |
%0m.n | 输出实数用到,一共有m位,小数占n位,不足m位用0左填充 |
%% | 一个% |
%Ns | 显示N个字符的字符串,不足N个用空格左填充 |
%-Ns | 显示N个字符的字符串,不足N个用空格右填充 |
%0s | 显示N个字符的字符串,不足N个用0左填充 |
特别要注意的是如果使用%s,则是挨个打印字符直到遇到’\0’结束。进行输出的时候,如果字符串数组中不含’\0’,则字符串的输出可能会内存溢出。
对于单个字符的输出,也可以使用putchar()
函数,使用方法如下:
putchar(ASCII码或者字符);
关于字符串输出的代码示例如下:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
void test1()
{
char ch = 'a';
printf("ch = %c\n", ch);
char str[] = "Hello World";
char str1[2] = { 'a', 'b' };
printf("str = %s\n", str);
// str1没有以'\0'结尾,字符串输出会内存溢出
printf("str1 = %s\n", str1);
}
void test2()
{
char str[] = "Hello Wolrd";
printf("|%-15s|\n", str);
}
void test3()
{
putchar(97); // 'a' -- 97
putchar('a');
putchar('b');
putchar('c');
putchar('d');
}
int main(int argc, char *argv[])
{
// test1();
// test2();
test3();
system("pause");
return 0;
}
#endif
对于数据的输入,我们常使用的是scanf()
函数,对于单个的字符,也常使用getchar()
函数。对于两者的使用方法如下:
scanf(格式字符串, 地址1[, 地址2, ...]);
变量 = getchar();
使用scanf的时候,一定要按照格式字符串中的形式来进行输入,前面有多少个格式字符,那么后面就需要跟多少个变量的地址去接收该数据。而getchar只需要调用就可以接收到数据,最后以返回值的形式返回直接赋值给变量就行。
对于使用%s来进行字符串的输入的时候,会产生一定的安全隐患,如果内存空间不足的,数据能够被存入内存,但是不被保护。比如使用一个长度为5的字符数组去存储"helloworld"这个字符串。这个能够被输入,但是只有"hello"能够被保护,因为字符数组长度为5,而对于剩下的"world\0"虽然能够存储下来,但是并没有被保护,后续操作也可能被其它操作所覆盖掉数据。scanf接收字符串的时候,碰到空格和换行就会自动终止,因此不能使用scanf去接收带有空格的字符串。
在VS中,如果直接使用scanf的时候会被警告编译失败。此时解决的方法有两个,如下:
// 方法1
#define _CRT_SECURE_NO_WARNINGS
// 方法2
#pragma warning(disable:4996)
上述的两句代码随便一句添加到C语言文件开头即可,一般使用的是方法一。关于格式化输入的示例代码如下:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
// 获取用户输入整数并输出
void test1()
{
int a;
scanf("%d", &a); //&表示取出变量a的地址。描述a的空间
printf("a = %d\n", a);
}
// 连续输入多个变量 char
void test2()
{
char ch1, ch2, ch3;
scanf("%c%c%c", &ch1, &ch2, &ch3);
printf("ch1 = %c\n", ch1);
printf("ch2 = %c\n", ch2);
printf("ch3 = %c\n", ch3);
}
// 连续定义多个变量,用空格隔开输入 int
void test3()
{
int a1, a2, a3;
scanf("%d %d %d", &a1, &a2, &a3);
printf("a1 = %d\n", a1);
printf("a2 = %d\n", a2);
printf("a3 = %d\n", a3);
}
// 用字符数组接收字符串输入
void test4()
{
char a[20];
scanf("%s", a);
printf("a = %s\n", a);
}
// 使用getchar和putchar输入和输出字符
void test5()
{
char ch;
ch = getchar(); // 返回接收到的ASCII码
printf("ch = %c\n", ch);
putchar(ch);
putchar('\n');
}
int main(int argc, char *argv[])
{
// test1();
// test2();
// test3();
// test4();
test5();
system("pause");
return 0;
}
#endif
C语言中常用的运算符有六种,分别是算术运算符、赋值运算符、比较运算符、逻辑运算符、位运算符、sizeof运算符。
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
() | 圆括号 | (表达式)/函数名(形参) | -- | ||
. | 成员选择(对象) | 对象.成员名 | -- | ||
-> | 成员选择 | 对象指针->成员名 | -- | ||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | -- | ||
sizeof | 长度运算符 | sizeof(表达式) | -- | ||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!=表达式 | |||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1?表达式2:表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | -- |
/= | 除后赋值 | 变量/=表达式 | -- | ||
*= | 乘后赋值 | 变量*=表达式 | -- | ||
%= | 取模后赋值 | 变量%=表达式 | -- | ||
+= | 加后赋值 | 变量+=表达式 | -- | ||
-= | 减后赋值 | 变量-=表达式 | -- | ||
<<= | 左移后赋值 | 变量<<=表达式 | -- | ||
>>= | 右移后赋值 | 变量>>=表达式 | -- | ||
&= | 按位与后赋值 | 变量&=表达式 | -- | ||
^= | 按位异或后赋值 | 变量^=表达式 | -- | ||
|= | 按位或后赋值 | 变量|=表达式 | -- | ||
15 | , | 逗号运算符 | 表达式,表达式,.... | 左到右 | -- |
对于算术运算符,是先乘除取模,然后才执行加减运算。除法运算和数学上的除法有一点区别,在C语言中的除法是将除后得到的结果取整数部分,小数部分直接舍弃,不存在四舍五入的情况。在除法和取模运算的时候,第二个操作数不能是0,这是不允许的。当然取模运算的第二个操作数也不能够是小数。当我们对负数进行取模运算的时候,得到的结果是余数的绝对值,如10对-3进行取模运算的时候结果为1。
对于++和–运算的时候,我们需要区分是前缀自增自减还是后缀自增自减运算。当是前缀自增自减运算的时候,先进行自增或者是自减运算,再进行取值操作;当是后缀自增自减运算的时候,先进行取值操作再进行自增或者自减运算。同时出现前缀和后缀的时候,一般来说后缀自增自减运算符要比前缀自增自减运算符的优先级要高,因为自增自减运算符的结合方向是从右往左的。
对于三目运算符(条件运算符)来说,表达式1是一个判别表达式,要能判断出真还是假。如果是真的则执行表达式2,否则执行表达式3。
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#define PI 3.14
void test1()
{
int a = 10;
int b = 20;
int c = a + b;
int d = 34 / 10;
printf("c = %d\n", c);
printf("d = %d\n", d);
}
void test2()
{
int a = 10;
int b = 50;
printf("a = %d\n", a ++);
printf("a = %d\n", a);
printf("b = %d\n", ++b);
printf("b = %d\n", b);
}
int main(int argc, char *argv[])
{
// test1();
test2();
system("pause");
return 0;
}
#endif
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
void test()
{
int a = 34;
int b = 0;
printf("!a = %d\n", !a);
printf("!b = %d\n", !b);
printf("a && !b = %d\n", a && !b);
printf("!a || b = %d\n", !a || b);
}
int main(int argc, char *argv[])
{
test();
system("pause");
return 0;
}
#endif
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
// 三目运算符
void test1()
{
int a = 40;
int b = 4;
int m = a < b ? 69 : a < b ? 3 : 5; // 嵌套的三目运算符
printf("m = %d\n", m);
printf("%d\n", a > b ? 69 : 100);
}
// 逗号运算符
void test2()
{
int a = 10, b = 20, c = 30;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
int x = (a = 1, c = 5, b = 2);
printf("x = %d\n", x);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
}
int main(int argc, char *argv[])
{
// test1();
test2();
system("pause");
return 0;
}
#endif
在C语言中,类型转换分为强制类型转换和隐式类型转换。隐式类型转换是由编译器自动完成的。转换的方向如下所示:
也就是假如一个char类型的变量与一个short类型的变量运算的时候,都会自动转换为signed int计算。如果是一个double类型的变量和一个float类型的变量进行计算,则需要把float类型的那个变量自动转换为double类型的变量才能进行计算。
除了运算过程中编译器自动完成隐式类型转换之外,在赋值的时候也会产生隐式类型转换的转换。在赋值的时候将小类型的数据赋值给大类型不会有任何问题,如果将大类型的数据赋值给小类型则可能会发生数据的丢失,比如将一个值为520的int类型的变量赋值给一个char类型的变量,高位就会丢失。
下面说一下强制类型转换,强制类型转换是程序员自己完成的。具体的语法如下:
(目标数据类型)待转换变量
(目标数据类型)待转换表达式
下面给出给出类型转换的示例代码:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
// 隐式类型转换
void test1()
{
int a = 321;
char ch = a; // 高位丢失
printf("ch = %d\n", ch);
}
// 强制类型转换
void test2()
{
float price = 3.6;
int weight = 4;
// double sum = (int)price * weight;
double sum = (int)(price * weight);
printf("价格: %lf\n", sum);
}
int main(int argc, char *argv[])
{
// test1();
test2();
system("pause");
return 0;
}
#endif
if语句是一个判断分支语句,根据匹配的一个范围来执行相应的语句块,流程图如下:
语法如下:
if(判别表达式1)
{
判别表达式为真,执行代码
}
else if(判别表达式2)
{
判别表达式1为假,并且判别表达式2为真,执行代码
}
else if(判别表达式3)
{
判别表达式1为假,判别表达式2为假,并且判别表达式3为真,执行代码
}
...
else
{
以上所有判别表达式都为假执行代码
}
上面语法中可以省略掉else if和else的内容,根据情况使用。下面给出关于if语句的示例:
#if 0
//#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:4996)
#include
#include
#include
void test1()
{
int a;
scanf("%d", &a);
if (a > 0)
{
printf("a > 0\n");
}
else
{
printf("a <= 0\n");
}
}
void test2()
{
int score;
printf("请输入学生的成绩: ");
scanf("%d", &score);
if (score >= 90 && score <= 100)
{
printf("优秀\n");
}
else if (score < 90 && score >= 70)
{
printf("良好\n");
}
else if (score < 70 && score >= 60)
{
printf("及格\n");
}
else
{
printf("不及格\n");
}
}
int main(int argc, char *argv[])
{
// test1();
test2();
system("pause");
return 0;
}
#endif
test1的目的是输入一个整数,输出它大于0还是小于等于0。test2的目的是根据输入的学生的成绩去判断学生位于哪一层次。其中90~100为优秀,70~89为良好,60~69为及格,60以下为不及格。
【三只小猪称体重】:屏幕输入三只小猪的重量,借助if语句,找出最重的小猪重量。
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main(int argc, char *argv[])
{
int pig1, pig2, pig3;
printf("请输入三只小猪的体重: ");
scanf("%d %d %d", &pig1, &pig2, &pig3);
if (pig1 > pig2)
{
if (pig1 > pig3)
{
printf("第一只小猪最重,体重为 %d\n", pig1);
}
else
{
printf("第三只小猪最重,体重为 %d\n", pig3);
}
}
else
{
if (pig2 > pig3)
{
printf("第二只小猪最重,体重为 %d\n", pig2);
}
else
{
printf("第三只小猪最重,体重为 %d\n", pig3);
}
}
system("pause");
return 0;
}
#endif
switch语句和if语句其实作用都差不多,但是switch语句是的条件是精准匹配,而不是一个范围内的匹配,根据匹配到的值决定switch语句从哪一个case开始执行。switch语法如下:
switch(判别表达式)
{
case 1:
执行语句1;
break;
case 2:
执行语句2;
break;
case 3:
执行语句3;
break;
....
case N:
执行语句N;
break;
default:
其他情况的统一处理;
break;
}
下面用之前if语句中对成绩层次判断的代码来作为示例,代码如下:
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main(int argc, char *argv[])
{
int score;
scanf("%d", &score);
switch (score / 10)
{
case 10:
case 9:
printf("优秀\n");
break;
case 8:
case 7:
printf("良好\n");
break;
case 6:
printf("及格\n");
break;
default:
printf("不及格\n");
break;
}
system("pause");
return 0;
}
#endif
在switch语句中,我们需要注意case穿透。在一个case分支中如果没有break,那么它会继续执行下一个case分支。
while语句和do-while语句都是循环控制语句。首先我们来看一下两者的流程图。首先是while的流程图如下:
do-while的流程图如下:
根据流程图可以发现两者的区别是while是先判断后执行,do-while是先执行后判断。无论条件是否真假,do-while都至少执行一次。关于两者的语法如下所示:
// while的语法
while(条件判别表达式)
{
循环体
}
// do-while的语法
do
{
循环体
} while(条件判别表达式);
下面给出一个关于while循环使用的代码。
【敲7游戏】:在1~100的数字中,如果碰到该数字含有7或者为7的倍数则输出敲桌子,否则输出该数字。
#if 0
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main(int argc, char *argv[])
{
int num = 1;
while (num < 100)
{
if (num % 7 == 0 || num % 10 == 7 || num / 10 == 7)
{
printf("敲桌子\n");
}
else
{
printf("%d\n", num);
}
num ++;
}
system("pause");
return 0;
}
#endif
下面给出关于do-while循环使用的示例,其中test1是输出1~10的整数,而test2是求100~1000之间的所有水仙花数。各个位上的数字的立方和等于本身的三位数就是水仙花数。
#if 1
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
// do-while循环的基础用法
void test1()
{
int a = 0;
do
{
a++;
printf("a = %d\n", a);
} while (a < 10);
}
// 求水仙花数
void test2()
{
int num = 100;
int ge, shi, bai;
do
{
ge = num % 10;
shi = num / 10 % 10;
bai = num / 100;
if (ge * ge * ge + shi * shi * shi + bai * bai * bai == num)
{
printf("%d\n", num);
}
num++;
} while (num < 1000);
}
int main(int argc, char *argv[])
{
// test1();
test2();
system("pause");
return 0;
}
#endif