快速上手C语言【上】(非常详细!!!)

目录

1. 基本数据类型

 2. 变量

2.1 定义格式 和 命名规范

2.2 格式化输入和输出(scanf 和 printf) 

​编辑

2.3 作用域和生命周期

 3. 常量

4. 字符串+转义字符+注释

5. 操作符

5.1 双目操作符

5.1.1 算数操作符 

5.1.2 移位操作符

5.1.3 位操作符 

5.1.4 赋值操作符

5.1.5 关系操作符

5.1.6 逻辑操作符

 5.1.7 下标引用、函数调用和结构成员

5.2 单目操作符

5.2.1 不改变原操作数

5.2.2 改变原操作数 

5.3 三目运算符

5.4 逗号表达式

5.5 操作符的属性

6. 整形提升和类型转换 

7. 分支语句和循环语句 

7.1 分支语句(选择结构)

7.1.1 if语句

7.1.2 switch语句

7.2 循环语句

7.2.1 while

7.2.2 for

7.2.3 do...while

7.2.4. goto语句 

7.3. 一段有趣的代码


1. 基本数据类型

  也叫 内置类型 ,即编程语言自带的基本数据类型,无需额外定义或导入,直接就能定义变量,如下:

  1:char //字符数据类型

  典型的比如 ASCII 字符表

  第一部分:(看看就行)

  第二部分:常用的就三小段:字符0 ~ 9,A ~ Z,a ~ z;只需记住0,A,a对应的十进制,往后递增加1,大小写间的十进制差是32。并且 char 类型(字符类型)的数据在参与表达式运算时用其对应的十进制数,所以也把它被归为 整形 一类,即数学中的整数。

  接着是:每个 char 类型数据的大小为1个字节(byte)。

  字节是计算机划分数据存储的基本单位,1个字节又划分成8个比特位(bit),即8个0或1组成的串, 比如,ASCII表用二进制表示就是:00000000 ~ 01111111;那么8个比特位能表示的数据范围就是 00000000~11111111,即0~255,所以是不是说 char 可表示的十进制数据范围就是0~255?

  其实不是的。

  char 可定义变量的有效数据范围是:-128  ~ 127;0~255指 unsigned(无符号的) char 的范围,这是一种 无符号型的数据,char是有符号的。现在你只需要知道的是:不同类型可定义变量的有效数据范围是不同的,范围外的就叫 “溢出”。

  同属整形家族还有:

  2. short  //短整型,2byte == 16bit,范围:-2^15 ~ 2^15 - 1,即-32768 ~ 32767

      unsigned short 范围 0 ~ 2^16,即0~65535

  3. int   //整形,定义整数常用,4byte == 32bit,范围:-2^31 ~ 2^31 -1,即:正负21亿多 

      unsigned int 范围 0~2^32,即42亿多;但是经常使用的是:size_t(无符号整形),注意 其在32(x86)位系统下,4个字节;在64(x64)位系统下,8个字节。

  4. long //长整形,在 32(x86)位系统上,通常是 4 byte;在 64(x64)位系统上, 通常是 8 byte

  5. long long //更长的整形,8byte

  然后是浮点数,和整形的规则不同,现在大家把它们当 普通小数类型 来用就行

  6. float   //单精度浮点数,4byte

  7. double //双精度浮点数,8byte

  8. 指针类型(后面讲) 

 2. 变量

2.1 定义格式 和 命名规范

  定义格式:数据类型  变量名;

  形象的解释一下就是:拿着图纸造房子 ——> 不同的设计图纸对应不同的数据类型,造出来的房子是一个个实体呀,属于开了物理空间的,其大小就对应着设计图纸的参数,也就是数据类型的大小,比如int有4个字节,char只有一个字节; 至于这个房子里放什么,可能随着时间一直在变化,所以称 这个房子 就是个变量;但不管你放什么,都不能超过其大小,比如这个房子的大小就200平,你偏要往里修个1000平的游泳池,放不下呀,也就是我们前面说的 “溢出”了;还有,这个房子 最后叫什么名,也不是设计图纸要管的事,属于开发商的自定义,即 变量名

  【***.c文件叫源文件,就是写代码的地方;一切的字母和符号都要用 英文输入法;“ ; ” 是一条语句结束的标志,不可遗漏】

  比如,在 test.c 源文件中: 

int main()//程序执行入口,只能有一个,一定要写!!!
{char a, b = 'b', c, d;//定义并局部初始化;定义多个变量用逗号隔开;单个字符的使用要用单引号' 'int age = b-18;//定义并赋值(=)初始化,这是好的编程习惯;编译器默认向上查找变量b,就是说:一定要:先定义,再使用!!!float weight = 53.5f;//小数默认为double类型,定义float类型可带后缀freturn 0;//返回执行结果
}

 命名规范有以下要求:

  1.只能由字母(包括大写和小写)、数字和下划线( _ )组成。

  2.不能以数字开头。

  3.长度不能超过63个字符。

  4.变量名中区分大小写的。

  5.变量名不能使用关键字(语言本身预先设定好的),比如:

2.2 格式化输入和输出(scanf 和 printf) 

 包含头文件:#include<stdio.h>

 (关于<头文件(***.h)>,现在你只要知道是:C语言本身已经写好的东西(比如下面的scanf和printf)都是被打包好的,你安装C编译环境时,它们就被下载到你的本地PC上;你要用的时候,就要告诉编译器去哪里找,方式就是:#include<****.h>) 

 输入:scanf

 (如果你用Visual Studio,加上:#define _CRT_SECURE_NO_WARNINGS 1  否则错误信息会建议你使用 scanf_s,但这个是微软自己搞的,不属于官方原生)

  从stdin(标准输入流,就是你的键盘输入,其实被放到一个类似文件的管理系统中,我们在屏幕上看到的输入就是从这个系统里读取出来,再呈现给我们)读取数据,并根据参数格式将其存储到附加参数(指针变量)所指向的位置,也就是我们定义好的变量

  比如:

  关于这个字符序列(C字符串):

  空白字符: 该函数将读取并忽略下一个非空白字符之前遇到的任何空白字符(空白字符包括空格、换行符\n和制表符\t。

 非空白字符,除了格式说明符(%): 都会导致scanf函数从流中(键盘)读取下一个字符,将其与该非空白字符进行比较,如果匹配,则丢弃该字符,继续读下一个字符。如果字符不匹配,则函数失败,返回并保留流的后续字符未读。

 格式说明符: 由初始百分比符号(%)组成的序列表示格式说明符,用于指定要从流中检索的数据的类型和格式,并将其存储到附加参数所指向的位置。

  常用的有:%c —— 字符类型

                    %d —— 有符号十进制整数

                    %u —— 无符号十进制整数

                    %f  —— 十进制单精度浮点数

                    %lf —— 十进制双精度浮点数

                    %s —— C字符串

                    %x —— 无符号十六进制整数(0~9,a~f,A~F),一般以0x或0X开头(0是数字零)

                    %o —— 无符号八进制整数(0~7)

   至于什么意思,看下面的例子:

  也就是说,我们的输入根本就不是我们以为的整型,浮点型,而都是文本字符,程序是根据 格式说明符 来处理读到的字符,修改变量内容的。 

  scanf 格式说明符的完整原型其实是下面这个: 

  %[*][width][length]说明符

  其中:* 表示一个“忽略”操作,即从stdin中读取字符,但是不对变量内容进行修改

             width:宽度,指定当前读取操作中要读取的最大字符数(加上终止符\0),遇到空格字符提前结束读取(可选) 

             length:指的是数据类型的长度修饰符,用于指定数据类型的大小,以便于正确地读取或打印数据;不同的 length 修饰符影响与类型相关的转换。其通常和说明符搭配使用:

  (黄色行表示C99引入的说明符和子说明符) 

  现在给大家举个测试例子:

  大家平常的使用场景,一般用不到[*][width][length],直接 "%+常用说明符" 就行,但这些丰富的用法总得见一见,学一学,用一用。

  接着是格式化输出:printf

  和scanf不同,其作用是:把C字符串写到stdout(标准输出流),即 屏幕上

  关于这个C字符串,有两种情况:

  情况一:不包含格式说明符(%):

        也可以输出中文字符:printf("你好,我的名字是***") 

  情况二:包含格式说明符(%),则其后面的附加参数(变量的值)将被格式化并插入到结果字符串中,以替换它们各自的说明符。常用格式说明符和scanf的一样,这里再加几个:

%p —— 指针(地址)

%e(小写)和 %E(大写)—— 科学计数法,通常对浮点数使用。

比如:12345.6789 科学计数法表示为:1.23456789 x 10^4

           %e格式输出就是:1.234568e+04 【默认保留6位小数,并且四舍五入;e+04表示将小数点向右移动 4 位,就是 12345.680000,较原数产生了精度损失】

           -0.0034 科学计数法表示为:-3.4 x 10^-3

           %E格式输出就是:-3.400000E-03 【默认保留6位小数,并且四舍五入;E-03表示将小数点向左移动 3 位,就是 -0.003400,较原数没有精度损失】

  如下示例:

int main()//执行入口
{int age = 18;double weight = 56.5;const char* name = "ZhangSan";//常量字符串的定义初始化,先学着用printf("Name=%s;Age=%d;Weight=%lf\n", name, age, weight);printf("addree(age)=%p\n", &age);double d1 = 12345.6789;double d2 = -0.0034;printf("%e,  %E\n", d1, d2);return 0;
}

  输出:

  (至于为什么地址是一串数字编号,我们后面再看,现在你只要知道是什么就行)

  printf的格式说明符遵循以下原型:

  %[flags][width][.precision][length]说明符   

   和scanf一样,末尾的说明符字符是最重要的组成部分,其它的都是子说明符,属于可选可不选。

  1:先来看[width]:(每个输出只能二选一)

        number:要打印的最小字符数。如果要打印的值比这个数字短,结果将用空格填充。即使结果更大,值也不会被截断

        *: 宽度不是在格式字符串中指定的,而是作为必须格式化的参数之前的附加整数值参数指定的。

  比如:

   并且通过num1的输出可以发现:默认右对齐

   如果要左对齐,就要添加 flags 标志。

  2:[flags](可任意组合使用)

  2.1 如果不写入任何符号,则在值之前插入空白,不是空格

  2.2 ‘ - ’:左对齐

  2.3 ‘+’:即使是正数,也强制在结果前加上正号(+),因为默认情况下,只有负数前面有-号

        比如:输入一个整数,要求输出占20个字符,左对齐,带正负号

int num = 0;
scanf("%d", &num);
printf("num=%-+20d\n", num);//或者printf("num=%+-*d\n", 20, num);

  如果就是要求默认的右对齐,但是不够的字符要指定用零(0)而不是空格左填充,就要用符号:

  2.4 ‘0’ 字符零,修改为如下:

printf("num=%+020d\n", num);//或者printf("num=%0+*d\n", 20, num);

  (注意:左对齐时,指定填充就失效了) 

  2.5 ‘#’:

  2.5.1 与o、x或X说明符一起使用时,对于不同于零的值,值的前面分别加0、0x或0X

        比如:

int num4 = 100;
printf("%#o, %#x, %#X\n", num4, num4, num4);

        输出:0144, 0x64, 0X64

  2.5.2 如果与 g 或 G 一起使用时,即使后面没有更多数字,它也会强制写入输出包含小数点;说明符‘g'和'G'的意思是:使用最短形式表示浮点数。

        比如:

double num5 = 100.00000;
printf("%lf, %g, %#g\n", num5, num5, num5);float num6 = 100.12056;
printf("%lf, %G, %#G\n", num6, num6, num6);

        输出:   'g'说明符会根据数值的大小自动选择使用 常规小数形式%f)或 科学计数法%e),以确保输出尽可能简洁。具体来说,%g 会根据浮点数的大小和精度选择最合适的格式,去除不必要的尾随零和小数点。

   3. [.precision] 精度 (每个输出只能二选一)

     .number

        对于整数说明符(d, u, o, x, X):效果相当于[flags]的'0'指定左填充;精度为0表示无操作。

     (常用)对于浮点数(l, lf, e, E): number就是小数点后要打印的位数(默认是6位)

        对于'g'和’G'说明符:有效数字的最大数目

        对于's'说明符:这是要打印的最大字符数。默认情况下,将打印所有字符,直到遇到结束的\0字符。 如果指定的周期没有明确的精度值,则假定为0

   .*:精度不是在格式字符串中指定的,而是作为必须格式化的参数之前的附加整数值参数指定的

   看下面的例子:

int num7 = 10;
printf("%.0d,%.1d,%.*u\n", num7, num7, 10, num7);float num8 = 67.34508;
printf("%f, %.2f; %E, %.*E\n", num8, num8, num8, 3, num8);printf("%g, %.1g, %.*g, %.4g\n", num8, num8, 2, num8, num8);//有效数字是从左往右第一个非零数开始const char* str = "hello world!";
printf("%s, %.s, %.3s, %.20s", str, str, str, str);

  输出:

  4:[length] 长度 

  和前面scanf的[length]用法大致一致,此处不再赘述,贴一张参考表格:

2.3 作用域和生命周期

   变量分为:局部变量(变量所在的局部范围)  和  全局变量(整个项目代码)

   举个例:

int global = 10;//全局变量int main()
{int local = 20;//局部变量printf("global=%d, local=%d\n", global, local);return 0;
}

  总结一下就是:{ } 就叫一个局部域,这个域里定义的变量就叫局部变量,这些变量只能在这个域里使用(作用域),出了这个域,这些变量就会被销毁,即所占空间还给操作系统,不受你的代码程序管理了,从创建到销毁的时间段就叫做这个变量的生命周期;不在任何{}的变量,就是全局的,可被任意一个局部域使用,且其生命周期是程序结束时。

  其次,一个局部域可以通过{ }被不断划分成多级局部域;并且同级域内不能重定义重名,全局域也是如此;不同级局部域之间可以重名,因为生命周期不同。

  如下示例:

  最后,当局部变量和全局变量冲突时(重名),局部优先。 


int agc = 10;
int main()
{int agc = 20;printf("agc=%d\n", agc);//输出:agc=20return 0;
}

 3. 常量

  不同于变量,比如:年龄,体重,薪资等; 生活中的有些值是不变的,比如:圆周率,身份证号码,血型,......

  C语言中的常量和变量的定义的形式有所差异,分为以下以下几种: 

  1. 字面常量:比如:3.14,100,'a',"hello world!",......

  2. const 修饰的常变量:本质还是变量,但是不能直接通过变量名修改其内容,比如:

  修饰局部变量:

int main()
{const int a = 10;//局部变量a = 20;//报错return 0;
}

  修饰全局变量:它会默认具有内部链接属性,即变量只在定义它的源文件中可见,其他源文件无法引用,看对比:

  3. #define 定义的标识符常量:就一个词 “直接替换”,比如:

#define NUM1 10
int main()
{#define NUM2 20int a = NUM1 + NUM2;//处理成:int a = 10 + 20;printf("%d\n", a);//输出:20return 0;
}

  4. 枚举常量:整形常量,关键字(enum),可穷举,比如:

enum Colour//颜色
{Red = 2,Green = 4,Yellow = 8
};int main()
{enum Option//选项{Stop,//默认为0,往下递增+1Pass,Caution};printf("%d :> %d\n%d :> %d\n%d :> %d\n", Red, Stop, Green, Pass, Yellow, Caution);return 0;
}

  输出:

   这里只是简单介绍让大家能看懂,学着用;实际的运用场景是更丰富,更灵活的,需要不断的项目代码积累量,才能体会。

4. 字符串+转义字符+注释

  关于 字符串,前面已经介绍过并使用过了,这里就简单提一下:

  用双引号 " " 引起来的一串连续字符,结束标志是'\0'

  定义一个常量字符串:const char* str = "*********";

  输出:printf("%s\n", str); 或者 printf(str); 

  一个常用的库函数:strlen,返回字符串的长度,即字符数,不包含‘\0' 

  包含头文件:#include<string.h>

  如下代码:

int main()
{const char* str = "hello world!";size_t len_str = strlen(str);printf(str);printf("\nlen_str=%u\n", len_str);return 0;
}

  输出: 

  下面,我们来看 转义字符:顾名思义就是转变意思

  常见的有:

  注: 

  三字符序列是 C 语言标准中为支持某些不包含全套 ASCII 字符集的键盘而引入的一组字符组合,这些组合会被编译器解释为其他符号。例如,??) 被解释为 ],而 ??( 被解释为 [。为了防止这些三字符序列在代码中意外地被编译器解释为其他符号,C 语言提供了 \? 这个转义字符。虽然三字符序列在现代编译器中已经很少使用,但 \? 作为一个转义字符仍然存在,以避免与这些序列发生冲突。

  看下面的例子:

int main()
{char c1 = '\'';printf("%c\n", c1);const char* str1 = "\"hello\", \"world\"\f";printf(str1);printf("\nD:\code\test.c\n");printf("D:\\code\\test.c\n\n");int i = 0;for (; i < 10; i++){printf("\a");}//\b将输出光标向左移动一个字符,但不会删除字符,但是会被紧接着的输出覆盖printf("start test char of '\\b'!\b\n");printf("start test char of '\\b'!\b\b\b\n");printf("start test char of '\\b'!\b\b\b\b\b\b\b****\n\n");printf("start test char of '\\r'!\n");printf("start test char of '\\r'!");printf("\rstart test\r char of '\\r'!\n\n");//覆盖printf("\130\t\x30\test\n");//补空格return 0;
}

        输出:

  (注意:输出的都是单个字符) 

  注释: 

  其实在前面的示例中小编就一直在用,目的之一就是:有些代码需要标注一下是干啥的;或者代码太多,避免逻辑混乱;再或者你要把代码给别人看/用,减少他人的理解成本;...... 这绝对是个利己利他,调高效率的编程好习惯! 

  另一个常见用途是,有的代码需要拿来做测试,但是发行版本又不需要,那么此时可以不用删除,注释掉就行。

  注释有两种风格(C/C++都能用):

        C语言风格的注释: /*xxxxxx*/

                缺陷:不能嵌套注释,即每个/*配对最近的*/

        C++风格的注释: //xxxxxxxx

                可以注释一行也可以注释多行 

  再简单举个例子: 

int main()
{/*int age = 10;const char* name = "zhangsan";double weight = 56.8;*///接下来是一段测试代码,探究scanf和printfint a = 10;//定义变量a并初始化为10scanf("%2d", &a);//只读取2个字符,当整数处理printf("%-+10.6o\n", a);//输出:左对齐,带正负号,占10位,精度为6,以八进制输出//......return 0;
}

   当然,这里只是演示,告诉大家怎么用,实际中的注释应该尽量言简意赅,重点突出!

5. 操作符

  是由操作数(如变量、常量)和操作符组成的一段代码,就叫 表达式,它会被计算并返回一个值;单独的一个操作数也可以被视为一个表达式,这种表达式的计算结果就是操作数本身。

5.1 双目操作符

  即,需要左右两个操作数。

5.1.1 算数操作符 

   +(加)        -(减)        *(乘)        /        %(取余数)

  %操作符只能用于整数间,比如:5%2 = 1;其它的整数,浮点数都行。

  对于 / 操作符:如果两个操作数都为整数,执行整数除法 取模(商),比如:5/2=2

                           只要有一个操作数是浮点数,则执行正常的浮点数相除,比如:5/2.0=2.5

5.1.2 移位操作符

  << 左移操作符

  >> 右移操作符

  移位操作符的操作数只能是整数,位指 “比特位” 

  需要预备知识:《C语言---数据的存储》点2.1和点2.2

  先看:<< 左移操作符

  代码验证一下:

int main()
{int num1 = 10;int num2 = num1 << 2;printf("num1=%d, num2=%d\n", num1, num2);return 0;
}

    输出:

  接着看:>>  右移操作符

   代码验证一下,你当前的编译器是逻辑右移,还是算数右移:

int main()
{int num3 = -1;int num4 = num3 >> 2;printf("num3=%d, num4=%d\n", num3, num4);return 0;
}

  小编当前演示示例是算数右移:

 警告: 对于移位运算符,不要移动负数位,这个是标准未定义的。 

5.1.3 位操作符 

  这里的位,同样是指 “比特位”。两个操作数也必须是整数。

  &:按位与,有0就是0

  | :按位或,有1就是1

  ^:按位异或,相同为0,相异为1

 需要预备知识:《C语言---数据的存储》点2.1和点2.2 

 如下示例: 

  代码验证一下:


int main()
{int num5 = -1;int num6 = 2;int num7 = num5 & num6;int num8 = num5 | num6;int num9 = num5 ^ num6;printf("num5=%d, num6=%d, num7=%d, num8=%d, num9=%d", num5, num6, num7, num8, num9);return 0;
}

   输出:

   运用示例:实现两个整数的交换

int main()
{int a = 0;int b = 0;scanf("a=%d, b=%d", &a, &b);//思路一:创建中间临时变量tmpint tmp = a;a = b;b = tmp;//要求:不能创建其它变量//思路二:运用加减法a = a + b;b = a - b;//b=a+b-b=aa = a - b;//a=a+b-a=b//(重点!!!)思路三:利用 ^a = a ^ b;b = a ^ b;//b=a^b^b = a ^ 0=a;特性1:两个相同值^为0,并与顺序无关;特性2:任何值与0异或,都不变a = a ^ b;//a=a^b^a=b^0=bprintf("a=%d, b=%d\n", a, b);return 0;
}

5.1.4 赋值操作符

  =

  前面就一直在用,覆盖变量原先的值

 double f1 = 34.5;f1 = 70.0;int a = 1;int b = 2;int c = 3;a = c = b * a + 4;//连续赋值,从右往左//但是同样的语义,可以写成:(更加清晰爽朗而且易于调试,推荐)c = b * a + 4;a = c;

   复合赋值符:

   +=:a += b 就是 a = a + b;

  同理还有: -=,*=,/=,%=,>>=,<<=,&=,!=,^=

5.1.5 关系操作符

  > 大于        >=大于或等于

  <小于        <=小于或等于

  !=不等于        ==等于

  关系比较的结果:0为假,非零为真

  如下代码:

int main()
{int a = 10;int b = 20;printf("%d, %d, %d, %d, %d, %d\n", a > b, a >=b, a < b, a<=b, a != b, a == b);return 0;
}

   输出:

5.1.6 逻辑操作符

  &&  逻辑与:左右两个表达式同时为真(非0)才返回真,否则为假(0)

  ||     逻辑或 :左右两个表达式只要一个为真,就返回真 

(从左往右) 

  如下代码:

int main()
{int c = 20;int d = 30;int e = c < d && c == 20;int f = c >= d && d == 30;int g = e || f;int h = f || d <= c;printf("%d, %d, %d, %d\n", e, f, g, h);return 0;
}

  输出: 

 5.1.7 下标引用、函数调用和结构成员

  分别在数组,函数,结构体部分讲解。

5.2 单目操作符

  即,只需要一个操作数。

5.2.1 不改变原操作数

  -             负值:将正值变负,负值变正

  +            正值:不能把负值变正,表示该数值为正,为了代码的可读性或语义明确性,但较少使用

  !          逻辑反操作:真变假,假变真

  ~           对一个数的二进制按位取反:0变1,1变0

  sizeof    操作数的类型长度(以字节为单位)

  (类型)  强制类型转换

  &(取地址)和 *(解引用)在 指针 部分细讲。

  如下代码:

int main()
{int a = 10;int _a = -a;int b = -20;int _b = +b;int c = _a > b;int c1 = !c;int c2 = !c1;int d = 1;//二进制:0~1int _d = ~d;//二进制:1~0,输出十进制为:-2printf("%d, %d; %d, %d; %d, %d, %d; %d, %d\n", a, _a, b, _b, c, c1, c2, d, _d);//常用:sizeof,注意我的书写形式int e = sizeof(char);double f = 3.14;int g = sizeof(f);printf("%d, %d\n", e, g);printf("%d, %d, %d\n", sizeof(int), sizeof(a), sizeof a);//强制类型转换(相近类型: 整形,浮点型,指针)int num1 = 10;double num2= (double)(num1 / 4);double num3 = (double)num1 / 4;printf("%lf, %lf\n", num2, num3);return 0;
}

  输出: 

5.2.2 改变原操作数 

  前置/后置: ++ , --  ;每次+/- 1

  直接来看例子: 

int main()
{int a = 10;int b = ++a;//前置++:a先加1:a = a +1==11;然后再把a赋值给b,为11printf("a=%d, b=%d\n", a, b);int c = b++;//后置++:先使用,把b赋值给c, 为11;b再加1:b = b+1==12printf("c=%d, b=%d\n", c, b);//前置/后置--,也是同样的道理int d = --c;printf("d=%d, c=%d\n", d, c);//10, 10int e = d--;printf("e=%d, d=%d\n", e, d);//10, 9return 0;
}

  输出: 

  上点难度(如果对运算符的优先级和结合性存疑,先去看一下点5.5): 

//输出结果是什么?
int main()
{int i = 0, a = 0, b = 2, c = 3, d = 4;i = a++ && --b || d++;//从左往右依次判断:表达式"a++":先使用,a==0,为假,a再加1==1,那么(a++ && --b)为假;接着看"d++",为真,所以i=1printf("i=%d, a=%d, b=%d, c=%d, d=%d\n", i, a, b, c, d);//1,1, 2, 3, 5i = --d && a-- || ++c;//"--d"先减1==4,再用,为真;"a--"先用==1,为真,那么(--d && a--)为真,后面的不再判断,i = 1, a再减1==0printf("i=%d, a=%d, b=%d, c=%d, d=%d\n", i, a, b, c, d);//1, 0, 2, 3, 4//计算i = c++ + b-- * --d - ++a;//c==3,先用其+(b--*i), 再c加1==4;其中,b-- * --d ==6,b==1, d==3;则(c++ + b-- * i)为9;++a为1,那么i=9-1==8printf("i=%d, a=%d, b=%d, c=%d, d=%d\n", i, a, b, c, d);//8, 1, 1, 4, 3return 0;
}

  输出: 

5.3 三目运算符

  也叫 条件表达式: exp1 ? exp2 : exp3;

  如果表达式exp1为真,则执行并返回表达式exp2的结果,否则返回exp3的结果。

  如下代码:

int main()
{int a = 10;int b = 20;int c = a >= b ? a = b : a += 1;printf("c=%d, a=%d, b=%d\n", c, a, b);int d = a <= b && a != b ? a = b : b - 1;printf("d=%d, a=%d, b=%d\n", d, a, b);return 0;
}

  输出: 

5.4 逗号表达式

  就是用 ( ) 括起来,并用逗号隔开多个表达式:(exp1, exp2, exp3, …, expN); 

  从左向右依次执行,整个表达式的结果是最后一个表达式的结果。

  如下代码:

int main()
{int a = 10;int b = 20;int c = (a--, b + a, --b, a += a, b);//(10,  20+9==29, 19, a = a+a==9+9==18, 19) == 19printf("a=%d, b=%d, c=%d\n", a, b, c);return 0;
}

   输出:

5.5 操作符的属性

 复杂表达式的求值有三个影响的因素:

 1. 操作符的优先级。即,先算哪个后算哪个,比如a+b*c,先算乘再算加

 2. 操作符的结合性。比如: 判断 a!=b,从左往右;赋值 a=b,从右往左

 3. 是否控制求值顺序。比如:(a+b)*c,先算括号里的加,再算括号外的乘

 点3是我们自己控制的,C语言只规定了点1和2,如下表:

  两个相邻的操作符先执行哪个?取决于他们的优先级;如果两者的优先级相同,取决于他们的结合性。

  但是还是会产生一些问题表达式:不能确定唯一的计算路径! 

  比如:a*b + c*d + e*f

  在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行 ,因此此表达式的计算路径可能是:

  1:

        a*b

        c*d

        a*b + c*d

        e*f

        a*b + c*d + e*f

  2:

        a*b

        c*d

        e*f

        a*b + c*d

        a*b + c*d + e*f

  如果,abcdef都是单个变量/常量值且互不影响,那么不管哪个顺序都不会影响最终结果;

  但是,如果是复合表达式呢?

  比如:c + --c;

  操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得 知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

  同样的还有:int i = 10; int ret = i-- - --i * ( i = -3 ) * i++ + ++i;

                        int i = 1; int ret = (++i) + (++i) + (++i);

  那么,怎么算,就要看具体的编译器了,我们来对比一下VS2022和gcc: 

int main()
{int c = 10;int d = c + --c;printf("c=%d, d=%d\n", c, d);int i1 = 10;int ret1 = i1-- - --i1 * (i1 = -3) * i1++ + ++i1;printf("i1=%d, ret1=%d\n", i1, ret1);int i2 = 1;int ret2 = (++i2) + (++i2) + (++i2);printf("i2=%d, ret2=%d\n", i2, ret2);return 0;
}

   输出对比:

  所以,我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。 

6. 整形提升和类型转换 

  已经总结在小编的另一篇文章:《C语言---数据的存储》点击直达 (到这,你已经可以通篇学习此链接文章了) 

7. 分支语句和循环语句 

  程序的执行流程有3种结构,分别是:顺序结构(从上往下,依次执行),选择结构(条件判断),循环结构 

7.1 分支语句(选择结构)

7.1.1 if语句

  语法结构: 

//无分支
if (表达式)
{语句;
}//单分支
if (表达式)
{语句1;
}
else
{语句2;
}//多分支    
if (表达式1)
{语句1;
}
else if (表达式2)
{语句2;
}//......
/*
else if (表达式n)
{语句n;
}
*/else
{语句;
}

   注意:1:条件判断,即为表达式的真假,0表示假,非0表示真,真则执行语句块{ };并且之后的所有分支都不进行判断执行。

              2:如果不加 { }, 则表达式为真后 只默认执行一条语句!

  如下示例代码:

int main()
{int opt1 = 0;printf("请选择:1(继续) / 0(结束):>");scanf("%d", &opt1);if (1 == opt1)//"=="判断是否相等{int opt2 = 0;printf("请选择:1(好好学习) / 2(有米,不慌) / 3(摆烂):>");scanf("%d", &opt2);if (1 == opt2){printf("好Offer!\n");}else if (2 == opt2){printf("继承家产!\n");}else{printf("要去卖红薯了哟!\n");}}return 0;
}

        3. else是和它离的最近的if匹配的,所以适当的使用{}可以使代码的逻辑更加清楚,是种好的代码风格。否则如下示例:

int main()
{int a = 0;int b = 2;if (a == 1)if (b == 2)printf("hehe\n");else printf("haha\n");return 0;
}

  事与愿违,没有输出!

7.1.2 switch语句

  语法结构: 

switch (整型表达式)
{
case 整形常量表达式://其结果和switch()中的表达式结果进行匹配;如果匹配就执行;不匹配就下一个case继续比较空 / 一条 / 多条语句;break;//跳出switch分支结构,如果不加,将往下执行所有case分支,直到遇到break或结束//如果表达式的值与所有的case标签的值都不匹配,那么没有任何一个case分支被执行//如果你并不想忽略掉不匹配所有标签的表达式的值时,可以加一条default子句default://位置是任意的(一般都放在末尾),但只能有一条,//......break;//好的编程习惯,最后一条语句都加break
}

  如下示例:

int main()
{int opt = 0;printf("请选择:1(好好学习) / 2(有米,不慌) / 3(摆烂):>");scanf("%d", &opt);switch (opt){case 1:printf("好Offer!\n");break;case 2:printf("继承家产!\n");break;case 3:printf("要去卖红薯了哟!\n");break;default://可选//......break;}return 0;
}

或者: 

int main()
{int day = 0;scanf("%d", &day);switch (day){case 1:case 2:case 3:case 4:case 5:printf("Weekday:Live like an ox and a horse!\n");case 6:case 7:printf("Rest days:You've survived another week!");default://......break;}return 0;
}

   小练习:

int main()
{int n = 1;int m = 2;switch (n)//n==1{case 1:m++;//m==3case 2:n++;//n==2case 3:switch (n)//switch允许嵌套使用{case 1:n++;case 2:m++;//m==4n++;//n==3break;}case 4:m++;//m==5break;default:--n;break;}printf("m = %d, n = %d\n", m, n);return 0;
}

7.2 循环语句

7.2.1 while

  语法结构:

 while (表达式)//为真(非0)就继续,假(0) 就结束 while { }{//被循环执行的语句;//......//break; 语句:直接提前跳出 一层while循环//......//continue; 语句:终止本次循环,跳到while()表达式部分}

  如下代码:

#include<stdlib.h>
#include<time.h>#define Money 100 //假设你有100元
#define UnitPrice 7 //假设每张彩票单价7元int main()
{//随机数种子srand(time(NULL));//包含头文件:stdlib.h,time.hint opt1 = 0;printf("请选择:0【退出】,1【继续】:>");scanf("%d", &opt1);if (1 == opt1){int money = Money;int flag = 1;//标记是否因为余额不足而退出循环int opt2 = 0;printf("请输入你要购买的彩票号(整数0~5):>");while (scanf("%d", &opt2))//scanf返回读到的数据个数{int answer = rand() % 6;//产生0~5的随机数,可自定义if (opt2 == answer){printf("恭喜你,中奖500万!");break;}//没有中int opt3 = 0;printf("很遗憾,没有中!【当前余额:%d】\n接下来,你是要好好学习成为大牛【1】;还是当菜鸟,继续买彩票【0】请选择:>", money-=UnitPrice);scanf("%d", &opt3);if (1 == opt3){printf("汗水会助力每一个梦!");break;}//继续买彩票if (money < UnitPrice){printf("余额不足,还是好好学习吧!\n");flag = 0;break;}printf("请输入你要购买的彩票号(整数0~5):>");}//如果中彩票或者成为大牛if (1 == flag){printf("人生巅峰,迎娶白富美!\n");}}return 0;
}

  有兴趣的话,你可以拷贝到你的编译环境下玩一下,运气好的话,可能一次就中,但也可能一次不中,尽管只要在 规定范围的6个随机数里猜一个。而现实生活中,规则更复杂,数字是组合的,且随机性更大...... 可想而知,能中几十万,几百万的概率得有多低了。

  在Debug(调试模式)或 Release(可发行模式)下运行完代码后,在当前项目目录下会产生 ***.exe可执行文件

   双击就可执行,也可发给别人运行。

7.2.2 for

  语法结构: 

for (表达式1; 表达式2; 表达式3)
{//循环语句......://break; 语句:直接跳出一层for循环//continue;语句:终止本次循环,跳过其后的循环语句,直接到for(......)
}
//同样:如果不写{},默认只执行紧跟着的一条语句

   说明:表达式1为初始化部分,用于初始化循环变量

              表达式2为条件判断部分,用于判断循环是否继续,真(非0)就继续,假(0)就终止

              表达式3为调整部分,用于循环条件的调整

  第一次执行for(......),先执行表达式1,再执行表达式2判断,表达式3不执行;第二次及其以上执行for (......),表达式1不执行,先执行表达式3,再执行表达式2判断。

  比如: 

//打印1~n的素数
int main()
{int n = 0;printf("请输入打印范围(1~n) :>");scanf("%d", &n);int i = 0;for (i = 1; i <= n; ++i){//素数判断:大于1的自然数,除1和它本身不能被任何数整除if (i > 1){int j = 0;for (j = 2; j < i; j++){if (i % j == 0){break;//不是素数}}//执行到这,有两种情况:1:for正常结束,j == i,为素数; 2:中途break,不是素数if (j == i){printf("%d ", i);}}}
}

  测试输出:

  接着来看一些for循环的变种写法: 

for (; ; )//三个表达式都为空:死循环
{//......
}int i = 0;
for (; i < 10; )
{i += 3;//调整部分还可以放到{ }中
}int k = 0;
for (i = 1, k = 6;i < 3, k >= 1 ; i++, --k)//每一部分都可以是 逗号分隔的组合表达式,即逗号表达式
{printf("%d ", k);
}

  ......

  总之就是:非常灵活。 

  现在,如果要把7.2.1 的示例代码改成for结构,只需要修改一个地方: 

for (; scanf("%d", &opt2); )
{//......
}

7.2.3 do...while

  语法结构:

do
{//循环语句;//break; 语句:直接跳出//continue;语句:跳到表达式判断部分} while (表达式);//真(非0)继续循环;假(0)终止

   先至少执行一次循环语句之后,再while判断是否继续循环。

  比如,现在来写个猜数字的小游戏:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>int main()
{int input = 0;srand(time(NULL));do{//打印菜单printf("**********************************\n");printf("*********** 1.play     **********\n");printf("*********** 0.exit     **********\n");printf("**********************************\n");//输入printf("请选择>:");scanf("%d", &input);int random_num = rand() % 100 + 1;//生成1~100的随机数int num = 0;switch (input){case 1://开始游戏逻辑while (1){printf("请输入猜的数字>:");scanf("%d", &num);if (num > random_num){printf("猜大了\n");}else if (num < random_num){printf("猜小了\n");}else{printf("恭喜你,猜对了\n");break;}}break;case 0:break;default:printf("选择错误,请重新输入!\n");break;}} while (input);return 0;
}

7.2.4. goto语句 

  适用场景:在深度嵌套中,一次可跳出多层循环,到达任意指定的位置

  比如:

for(...)for(...){for(...){if(disaster)goto error;}}…
error://......

  除此之外, goto 可能使得代码执行流程变得复杂,会让人困惑,尤其是在大型程序中,可能产生不可达代码和资源管理问题,降低可维护性

  goto语句都能改成其它循环语句,因此,尽量避免使用 goto!

  这里用goto 写个关机小程序: 

#include <stdio.h>
#include<string.h>
#include<Windows.h>
int main()
{char input[10] = { 0 };system("shutdown -s -t 300");//秒printf("电脑将在5分钟后关机,如果输入:“cat!” 就取消关机!\n请输入:>");
again:scanf("%s", input);getchar();if (0 == strcmp(input, "cat!"))//比较字符串是否相等{system("shutdown -a");}else{printf("请重新输入:>");//故意输错,进行测试goto again;}return 0;
}

  如何改为其它循环语句,就交给你吧。

7.3. 一段有趣的代码

(声明:纯测试,无任何恶意!) 

  弹窗消息:

#include<windows.h>
int main()
{while (1){// 显示带有“确定”和“取消”按钮的消息框int result = MessageBoxW(NULL, L"叫爸爸!", L"今日问候:", MB_OKCANCEL | MB_ICONQUESTION);// 根据用户选择的按钮执行相应的操作if (result == IDOK){break;}// 用户点击“取消”MessageBoxW(NULL, L"大胆,逆子!", L"Result", MB_OK);}// 用户点击“确定”MessageBoxW(NULL, L"这就对了嘛,爸爸给你买糖吃!", L"Result", MB_OK);return 0;
}

  如果你使用vscode:在终端编译时:gcc .c源文件 -o .exe程序名 -mwindows 

  以窗口应用程序运行,而不是控制台应用程序。

  如果你使用Visual Studio:

     然后修改代码:

//在 Windows 应用程序中,使用 WinMain 作为入口点,而不是 main。
//这可以帮助编译器更好地识别你的程序为 GUI 应用程序。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) 
{//代码
}

  然后就可以把生成的可执行程序 发送给你的 好厚米 了。

  关注小编,持续更新中! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/439769.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为Floorp浏览器添加搜索引擎及搜索栏相关设置. 2024-10-05

Floorp浏览器开源项目地址: https://github.com/floorp-Projects/floorp/ 1.第一步 为Floorp浏览器添加搜索栏 (1.工具栏空白处 次键选择 定制工具栏 (2. 把 搜索框 拖动至工具栏 2.添加搜索引擎 以添加 搜狗搜索 为例 (1.访问 搜索引擎网址 搜狗搜索引擎 - 上网从搜狗开始 (2…

Java 网络编程基础

网络通信三要素 此笔记来之与黑马.B站的视频是真的高 基本的通信架构 基本的通信架构有2种形式&#xff1a;CS架构&#xff08;Client 客户端/ Server 服务端&#xff09;、BS架构( Browser 浏览器/ Server 服务端)。 IP 地址 IP&#xff08;InternetProtocol&#xff09;&a…

关于Zipf定律与TF—IDF的一个实践

在这篇文章中&#xff0c;我将通过机器学习中的线性回归来计算zipf定律中一个经验常数alpha&#xff0c;还会画TF-IDF的图像&#xff0c;此外还将简单介绍下与zipf、TF-IDF有关的知识。 在之前的一篇文章中我曾介绍过TF-IDF&#xff0c;但之后我又阅读了Ricardo Baeza-Yates和…

PELT算法

PELT算法的范畴 PELT算法&#xff08;Pruned Exact Linear Time&#xff09;属于时间序列分析和变点检测&#xff08;Change Point Detection&#xff09;范畴的算法。 从更广泛的角度来看&#xff0c;PELT算法还可以归类为以下几类算法的子集&#xff1a; 1. 时间序列分析&…

【数据结构】什么是红黑树(Red Black Tree)?

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f4cc;红黑树的概念 &#x1f4cc;红黑树的操作 &#x1f38f;红黑树的插入操作 &#x1f38f;红黑树的删除操作 结语 &#x1f4cc;红黑树的概念 我们之前学过了…

codetop标签树刷题(四)!!暴打面试官!!!!

用于个人复习 1.二叉树的右视图2.二叉树最大宽度3.二叉树的最大深度4.N叉树的最大深度5.二叉树的最小深度6.子树的最大平均值7.求根节点到叶节点的数字之和8.另一棵树的子树9.对称二叉树 1.二叉树的右视图 给定一个二叉树的根节点root&#xff0c;想象自己站在它的右侧&#x…

TypeScript 封装 Axios 1.7.7

随着Axios版本的不同&#xff0c;类型也在改变&#xff0c;以后怎么写类型&#xff1f; yarn add axios1. 封装Axios 将Axios封装成一个类&#xff0c;同时重新封装request方法 重新封装request有几个好处&#xff1a; 所有的请求将从我们定义的requet请求中发送&#xff…

setTimeout,setInterval ,requestAnimationFrame定时器

setTimeout&#xff0c;setInterval &#xff0c;requestAnimationFrame定时器 定时器函数通常用于执行定时任务&#xff0c;也就是说你做了一个功能放在定时器函数里&#xff0c;它可以在特定的时间去执行你的指令&#xff0c;或者说隔多长时间&#xff08;单位时间内—毫秒为…

Centos Stream 9备份与恢复、实体小主机安装PVE系统、PVE安装Centos Stream 9

最近折腾小主机&#xff0c;搭建项目环境&#xff0c;记录相关步骤 数据无价&#xff0c;丢失难复 1. Centos Stream 9备份与恢复 1.1 系统备份 root权限用户执行进入根目录&#xff1a; cd /第一种方式备份命令&#xff1a; tar cvpzf backup.tgz / --exclude/proc --exclu…

计算机系统基础概述

什么是计算机&#xff1f; 计算机是一种利用电子技术进行信息处理的设备&#xff0c;它能够接收、存储、处理和提供数据。计算机通过执行一系列预定义的指令来处理数据&#xff0c;这些指令通常被称为程序。计算机的核心功能包括算术运算、逻辑判断、数据存储和信息检索 计算…

STM32 通用定时器

一、概述 STM32内部集成了多个定时/计数器&#xff0c;根据型号不同&#xff0c;STM32系列芯片最多包含8个定时/计数器。其中&#xff0c;TIM6、TIM7为基本定时器&#xff0c;TIM2~TIM5为通用定时器&#xff0c;TIM1、TIM8为高级控制定时器。 1.定时器的类型 基本定时器通用定…

微信小程序-npm支持-如何使用npm包

文章目录 1、在内建终端中打开2、npm init -y3、Vant Weapp4、通过 npm 安装5、构建 npm 1、在内建终端中打开 Windows PowerShell 版权所有 (C) Microsoft Corporation。保留所有权利。尝试新的跨平台 PowerShell https://aka.ms/pscore6PS C:\Users\dgq\WeChatProjects\minip…

python泵站设备运行预警信息管理系统

目录 功能介绍具体实现截图技术栈和环境说明python语言解决的思路性能/安全/负载方面核心代码部分展示详细视频演示源码获取方式 功能介绍 用户端 注册登录&#xff1a;用户可以注册账号并登录系统。 西电泵站简介&#xff1a;提供泵站的历史、功能和重要性等详细介绍。 泵站…

余承东直播论道智能驾驶:激光雷达不可或缺,华为ADS 3.0引领安全创新

华为余承东:激光雷达,智能驾驶安全性的关键 9月29日,华为消费者业务集团CEO余承东在一场引人注目的直播中,与知名主持人马东就智能驾驶技术的最新进展进行了深入交流。在这场直播中,余承东针对激光雷达在智能驾驶中的必要性问题,发表了明确且深刻的观点,引发了业界和公众…

在Docker中运行微服务注册中心Eureka

1、Docker简介&#xff1a; 作为开发者&#xff0c;经常遇到一个头大的问题&#xff1a;“在我机器上能运行”。而将SpringCloud微服务运行在Docker容器中&#xff0c;避免了因环境差异带来的兼容性问题&#xff0c;能够有效的解决此类问题。 通过Docker&#xff0c;开发者可…

角色动画——RootMotion全解

1. Unity(2022)的应用 由Animtor组件控制 在Animation Clip下可进行详细设置 ​ 官方文档的介绍(Animation选项卡 - Unity 手册) 上述动画类型在Rag选项卡中设置: Rig 选项卡上的设置定义了 Unity 如何将变形体映射到导入模型中的网格&#xff0c;以便能够将其动画化。 对于人…

Linux驱动开发——LED驱动开发

文章目录 1 概述1.1 说明 2 基础知识2.1 地址映射2.1.1 ioremap函数2.1.2 iounmap函数 2.2 I/O内存访问函数2.2.1 读操作函数2.2.2 写操作函数 3 硬件原理图分析4 RK3568 GPIO驱动原理4.1 引脚复用设置4.2 引脚驱动能力配置4.3 GPIO输入输出设置4.4 GPIO引脚高低电平设置 5 实验…

【GeekBand】C++设计模式笔记5_Observer_观察者模式

1. “组件协作”模式 现代软件专业分工之后的第一个结果是“框架与应用程序的划分”&#xff0c;“组件协作”模式通过晚期绑定&#xff0c;来实现框架与应用程序之间的松耦合&#xff0c;是二者之间协作时常用的模式。典型模式 Template MethodStrategyObserver / Event 2.…

HarmonyOS/OpenHarmony 自定义弹窗页面级层级控制解决方案

关键词&#xff1a;CuntomDialog自定义弹窗、SubWindow子窗口、页面级、弹窗层级控制、鸿蒙、弹窗展示层级异常 问题存在API版本&#xff1a;API10 - API12&#xff08;该问题已反馈&#xff0c;期望后续官方能增加页面级控制能力&#xff09; 在正常的鸿蒙app开发过程中&…

aws(学习笔记第二课) AWS SDK(node js)

aws(学习笔记第二课) 使用AWS SDK&#xff08;node js&#xff09; 学习内容&#xff1a; 使用AWS SDK&#xff08;node js&#xff09; 1. AWS SDK&#xff08;node js&#xff09; AWS支持多种SDK开发(除了AWS CLI&#xff0c;还支持其他的SDK) AndroidPythonNode.js(Javas…