最全C语言知识点——整合(持续更新)

一直以来都是零零散散的去书写一些学习C语言的相关知识点,今天决定将所学习的已经发表和没有发表的所有知识点进行一个整合,方便自己,也适用于更多学习C语言基础的同学,如有纰漏,欢迎指出,有则改之无则加勉。

文章目录

第壹部分:初识C语言和基础知识

一:什么是C语言程序设计

1. 什么是C语言?

C语言是一门通用的计算机编程语言,广泛的应用于底层的开发。C语言的设计目标是提供一种能以简易的方式编译,处理低级存储器,产生少量的机器码以及不需要任何运行环境支持便能够运行的编程语言。

2. 第一个的C程序

#include <stdio.h>
int main()
{
printf("hello new world!\n");
return 0;
}

3. 头文件的介绍

  1. #include <> 会在系统目录中查找头文件
  2. #include “ ” 先在当前项目目录之中查找,然后再去系统目录中查找
  3. 所以一般来说,如果使用的包含标准库头文件,就使用 <>,包含着自己的头文件的话就使用 “ ” 。

二:具体的数据类型

1. 都存在着那些类型?

char      字符数据类型
short     短整型
int       整形
long      长整型
long long 更长的整形
float     单精度浮点数
double    双精度浮点数

存在这么多的类型是为了更加丰富的表达我们在生活之中所遇见的各种值。

2. 每种类型的大小是多少?

这段程序则是为了我们更好的检验数据类型大小到底是多少最为正确的一种方式了。

#include <stdio.h>
int main()
{
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long double));
return 0;
}

当然我们也应该明白对于不同的系统(32位,64位)的话,所对应的数据类型的大小是不同的,因此对于很多时候我们所考虑的大小,是需要结合相关的计算机系统来判定的,下面我们直接给大家上图。
C语言中各类型数据的长度
一般来说对于这些相对的数据类型的大小也是要求我们尽可能地自己记住,避免在后期编程的时候出现问题。

3. 主要数据的范围

    类型       无符号(unsigned)           有符号(signedchar          0~+255                    -128~+127
    
    short        0~65535                  -32768~32768
    
    int        0~4294967295            -2147483648~+2147483648(21亿)
    

这是所罗列出我们使用频率尽可能会多一些的数据类型的取值范围,也是为了方便我们在后期更好的使用;除此之外,也是建议大家将2的(2-16)次方大概牢记一些,相信大多数朋友肯定应该是比较熟悉的 了吧。

4. 不同类型之间的转换

4.1 两种不同的类型转换

当操作数的类型不同,而且不属于基本数据类型时,经常需要将操作数转化为所需要的类型,这个过程即为强制类型转换。强制类型转换具有两种形式:显式强制转换和隐式强制类型转换。
4.1.1显式强制类型转换:通过一定语句和相关的前缀,将所给出的类型强制转换成为我们所需要的类型。

TYPE b = (TYPE) a;

其中,TYPE为类型描述符,如int,float等。经强制类型转换运算符运算后,返回一个具有TYPE类型的数值,这种强制类型转换操作并不改变操作数本身,运算后操作数本身未改变。
举例1

int n=0xab65char a=char)n;

上述强制类型转换的结果是将整型值0xab65的高端一个字节删掉,将低端一个字节的内容作为char型数值赋值给变量a,而经过类型转换后n的值并未改变。
4.1.2隐式强制类型转换:当算术运算符两侧的操作数类型不匹配的时候,会触发“隐式的转换”,先转换成相同的类型,之后再进行计算。
C在以下四种情况下会进行隐式转换:
1、算术运算式中,低类型能够转换为高类型。
2、赋值表达式中,右边表达式的值自动隐式转换为左边变量的类型,并赋值给他。
3、函数调用中参数传递时,系统隐式地将实参转换为形参的类型后,赋给形参。
4、函数有返回值时,系统将隐式地将返回表达式类型转换为返回值类型,赋值给调用函数。

4.2 自动类型转换

在C语言中,自动类型转换遵循以下规则:
1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
4、char型和short型(在visual c++等环境下)参与运算时,必须先转换成int型。
5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分直接舍去。
一些需要注意的东西:
类型转换的规则
类型转换阶层图
在强制类型转换的时候需要注意的是,如(double)a,则可能会丢失掉原有的精度。
想要实现运算之后的四舍五入计算,只需要给一个数加上0.5就可以直接完成。

5. 整形截断和整型提升

5.1 整型截断

把四个字节的变量赋值给一个字节的变量,会存在截断,则对应的截取它低位上的值:

四字节变量
int 0x11111111 11111111 11111111 11111111;

截断后

一字节变量
char 0x1111 1111;

5.2 何谓整形提升

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

5.2 整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int
的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这
种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned
int,然后才能送入CPU去执行运算。
如:

char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中

5.3 如何进行整型提升

整形提升是按照变量的数据类型的符号位来提升的:
补充
( 因为在cpu之中只能够以

/负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111


/正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001


/无符号整形提升,高位补0

例1

int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0;
}

例子中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式
a= =0xb6 , b= =0xb600 的结果是假,但是c不发生整形提升,则表达式 c= =0xb6000000 的结果是真.
所程序输出的结果是:

c

例2

int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(!c));
return 0;
}

例2中的,c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) ,就是1个字节.

!!!截断提升相结合的例子!!!

#include <stdio.h>

int main()
{
	先进行截断
	原码 0x10000000 00000000 00000000 10000000
	反码 0x11111111 11111111 11111111 01111111
	补码 0x11111111 11111111 11111111 10000000
	a截断后内存中所存储的则是 1000 0000char a=-128;
    输出的是十进制,则意味着要进行提升(需要根据类型而定)
    0x11111111 11111111 11111111 100000000
    0x11111111 11111111 11111111 011111111
    0x10000000 00000000 00000000 100000000
	printf("%d\n",a);
	return 0;
}

三:常量和变量

1. 常量 变量的定义

生活之中有着一些值是保持不变的,比如圆周率,身份证,血型,性别等;而有些值却是可变的,比如我们的年龄,体重,薪资,和成绩。
在C语言之中,我们经常将那些不变的值定义为常量,也用常量这个概念名词来进行表示,那些变化的值我们使用变量这个概念名词来进行表示。

2. 常量

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

  1. 字面常量
  2. const修饰的常变量
  3. #define定义的标识符常量
  4. 枚举常量
    一个程序就让大家直接明白这些到底都是什么意思!
#include <stdio.h>
enum Sex
{
MALE,//枚举常量
FEMALE,
SECRET
};
int main()
{
3.14;//字面常量
1000;//字面常量
const float pai = 3.14f; //const 修饰的常量
pai = 5.14;//ok?
#define MAX 100 //#define的标识符常量
return 0;

3. 定义变量的方法

int age = 150;
float weight = 45.5f;
char ch = 'w';

对于后半部分所定义的值式可以进行改变,是完全由编程者来进行定义的一种数据。

4. 变量的分类

4.1 局部变量

局部变量又称之为内部变量,是由某对象或者某个函数所创建的变量,只能被内部引用,不能够被其他函数或对象引用。

4.2 全局变量

被整个程序所使用的变量称之为全局变量。

#include <stdio.h>
int global = 2019;//全局变量
int main()
{
int local = 2018;//局部变量
//下面定义的global会不会有问题?
int global = 2020;//局部变量
printf("global = %d\n", global);
return 0;
}

5. 变量在函数里的使用

#include <stdio.h>
int main()
{
int num1 = 0;
int num2 = 0;
int sum = 0;
printf("输入两个操作数:>");
scanf("%d %d", &a, &b);
sum = num1 + num2;
printf("sum = %d\n", sum);
return 0;
}
//这里介绍一下输入,输出语句 有一个了解
//scanf
//printf

6. 变量的作用域和生命周期

6.1 作用域

作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定
这个名字的可用性的代码范围就是这个名字的作用域。

  1. 局部变量的作用域是变量所在的局部范围。
  2. 全局变量的作用域是整个工程.

6.2 生命周期:

变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段

  1. 局部变量的生命周期是:进入作用域生命周期开始,出作用域生命周期结束。
  2. 全局变量的生命周期是:整个程序的生命周期。

四:字符串 转义字符 注释

1. 字符串

"hello bit.\n"

1.1 这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串。

1.2 字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串内容。

2. 转义字符:

顾名思义是转变意思,我们要在屏幕上打印一个目录: c:\code\test.c 我们该如何写代码?

#include <stdio.h>
int main()
{
printf("c:\code\test.c\n");
return 0;
}

实际上程序的运行结果是这样的:
转义字符运行结果
由于它存在着’\t’ 和’\n’ 所以打印出爱的时候是不存在这两个转义字符的。

3. 都有那些转义字符

转义字符 释义
\? 在书中写连续多个问号时使用,防止他们被解析成为三个字母词
\’ 用于表示字符串常量 ‘
\’’ 用于表示一个字符串内部的双引号
\\ 用于表示一个反斜杠,防止它被解释为一个转义序列符
\a 警告字符,蜂鸣
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ddd ddd表示1~3个八进制的数字。 如: \130
\xddd ddd表示3个十六进制数字。 如: \x030
#include <stdio.h>
int main()
{
//问题1:在屏幕上打印一个单引号',怎么做?
//问题2:在屏幕上打印一个字符串,字符串的内容是一个双引号“,怎么做?
printf("%c\n", '\'');
printf("%s\n", "\"");
return 0;
}

重点例题:不妨自己试试看

//程序输出什么?
#include <stdio.h>
int main()
{
printf("%d\n", strlen("abcdef"));
// \32被解析成一个转义字符
printf("%d\n", strlen("c:\test\32\test.c"));
return 0;

4. 注释

  1. 代码中有不需要的代码可以直接删除,也可以注释掉
  2. 代码中有些代码比较难懂,可以加一下注释文字
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
/*C语言风格注释
int Sub(int x, int y)
{
return x-y;
}
*/
int main()
{
//C++注释风格
//int a = 10;
//调用Add函数,完成加法
printf("%d\n", Add(1, 2));
return 0;
}

注释有两种风格:

  1. C语言风格的注释 /xxxxxx/
    缺陷:不能嵌套注释
  2. C++风格的注释 //xxxxxxxx
    可以注释一行也可以注释多行

五:内部存储结构

1. 内部存放形式

因为CPU只能够进行加法运算,是不是有点打破很多小程序猿固有的思维啊,所以对于整数来说,在内存之中是以补码的形式存放的。
符号位的表示

2. 原码反码补码

正数:原码是其本身 反码 补码和原码是完全相同的
  7  原码 0000 0111
     反码 0000 0111
     补码 0000 0111
 
负数:原码是其本身 反码符号位不变,其余位全部取反,补码则是在反码的基础上加1
 -7   原码 1000 0111
      反码 1111 1000
      补码 1111 1001
零:零分为正零+0 和负零 -0
  +0  原码 0000 0000
      反码 0000 0000
      补码 0000 0000
  -0  原码 1000 0000
      反码 1111 1111
      补码 0000 0000
补码往回转的话,依然是取反+1 切记!!!

3. 大小端

3.1 在计算机系统中是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit,但在C语言中还有其他类型的存在,因此要对这些多字节安排问题就导致了大端存储和小端存储。
3.2 大小端的故事
根据我了解到的好像是一个国家在吃一个食物的时候,众大臣讨论到底应该从大的一头开始吃,还是从小的一头开始吃起。
3.3 大小端区别
大端是和我们的认知是相符合的,则是从低到高开始进行排列。
小端的话则和我们的认知相反,它是从小往大的排列。

int 19 
在内存中的存储以大端序排列则为 00 01 00 11;
在内存中的存储以小端序排列则为 11 00 01 00

一个代码检测自己的电脑到底是大端序还是小端序

#include <stdio.h>

int main(){
    int x=1;
    char c=(char)x;
    if(c==1)
    {
    	printf("小端机\n");
    }
    else if(c==0)
    {
    	printf("大端机\n");
    }
    return 0;
}

4. 什么是ASCII码

ASCII 是我们所看见的字母和数字在内存之中所在的十进制地址值

第贰部分:操作符 关键字 表达式

一:操作符

1. 算数操作符

 +-*/%  求余
  1. 除了%操作符之外,其他的几个操作符是可以作用于整数和浮点数的。
  2. 对于 / 操作符,如果两个操作符都为正数的话,执行整数除法,而只要有浮点数存在就执行浮点数除法。
  3. % 操作符的两个操作符规定必须为整数,返回的则是整除之后所得到的余数。

2. 移位操作符

<< 左移操作符
>> 右移操作符
  1. 左移操作符移位规则:
    左边抛弃,右边补0
int num=10;

num<<1  (左移一位)

左移

  1. 右移操作符移位规则:
    首先右移运算分两种:
    1. 逻辑移位 左边用0填充,右边丢弃
    2. 算术移位 左边用原该值的符号位填充,右边丢弃

      例题
      逻辑右移:左边补0;
      逻辑右移
      算术右移:左边用原该值的符号位进行填充,由于是负数,所以符号位为1,即左边补1
      算术右移

3. 位操作符

位操作符有:

&  按位与 两者都为真结果才为真
|  按位或 两者只要存在一个真,结果则为真
^  按位异或 两者相同则为假,两者不同则为真
注:他们的操作数必须是整数

简单练习题

#include <stdio.h>
int main()
{
int num1 = 1;
int num2 = 2;
num1 & num2;
num1 | num2;
num1 ^ num2;
return 0;
}

3.1涉及到的短路求值

  1. 对于逻辑与运算来说,如果左侧的表达式的值已经确定是假的了,则此时整个表达式的值就已经确定了,右侧的表达式则再不需要进行求值。
  2. 对于逻辑或运算来说,如果左侧表达式的值已经是真的了,此时整个表达式的值也就确定了,右侧的表达式也不需要求值。

高难度练习题
要求:不能够创建临时变量(第三个变量),实现两个数的交换。

#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}

4. 赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。

4.1 复合赋值符

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符都可以写成复合的效果

例子

int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁

5. 单目操作符

单目操作符 含义
逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
- - 前置、后置- -
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
#include <stdio.h>
int main()
{
int a = -10;
int *p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//这样写行不行?
printf("%d\n", sizeof int);//这样写行不行?
return 0;
}

6. 关系操作符

>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等“

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。
!!! 注意: = 和== 的意义是不同的,用时需慎重

7. 逻辑操作符

&&    逻辑与
||    逻辑或

区分逻辑与和按位与 区分逻辑或和按位或

1&2----->0
1&&2---->1
1|2----->3
1||2---->

8. 条件操作符

exp1 ? exp2 : exp3
if (a > 5)
b = 3;
else
b = -3;
转换成条件表达式,是什么样?
2.使用条件表达式实现找两个数中较大值。

二:关键字

1. 常见关键字

auto break case char const continue default do
double else enum extern float for goto if int
long register return short signed sizeof
static struct switch typedef union unsigned void 
volatile while

2. 关键字typedef

typedef 顾名思义是类型定义,这里应该理解为类型重命名。

//将unsigned int 重命名为uint_32, 所以uint_32也是一个类型名
typedef unsigned int uint_32;
int main()
{
//观察num1和num2,这两个变量的类型是一样的
unsigned int num1 = 0;
uint_32 num2 = 0;
return 0;
}

3. 关键字static

在C语言中:
static是用来修饰变量和函数的

3.1 修饰局部变量

代码1
#include <stdio.h>
void test()
{
int i = 0;
i++;
printf("%d ", i);
}
int main()
{
for(i=0; i<10; i++)
{
test();
}
return 0;
}
//代码2
#include <stdio.h>
void test()
{
static int i = 0;
i++;
printf("%d ", i);
}
int main()
{
for(i=0; i<10; i++)
{
test();
}
return 0;
} 

对比代码1和代码2的效果理解static修饰局部变量的意义。
结论:static修饰局部变量改变了变量的生命周期,让静态局部变量出了作用域依然存在,到程序结束,生命周期才结束。

3.2 修饰全局变量

代码1
//add.c
int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}
代码2
//add.c
static int g_val = 2018;
//test.c
int main()
{
printf("%d\n", g_val);
return 0;
}

代码1正常,代码2在编译的时候会出现连接性错误。
结论:一个全局变量被static修饰,使得这个全局变量只能在本源文件内使用,不能在其他源文件内使用。

3.3 修饰函数

代码1
//add.c
int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}
代码2
//add.c
static int Add(int x, int y)
{
return c+y;
}
//test.c
int main()
{
printf("%d\n", Add(2, 3));
return 0;
}

代码1正常,代码2在编译的时候会出现连接性错误。
结论:一个函数被static修饰,使得这个函数只能在本源文件内使用,不能在其他源文件内使用。

4. define定义常量和宏

define定义标识符常量
#define MAX 1000
define定义宏
#define ADD(x, y) ((x)+(y))
#include <stdio.h>
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3);
printf("sum = %d\n", sum);
return 0;

使用宏的话:整体而言,宏的本质相当于文本的替换

  1. 定义一个常量
  2. 借助宏来重定义一个类型的别名
  3. 宏还能够影响到编译器的行为
  4. 宏还能够定义一个代码片段(类似于函数的效果)

三:表达式

1. 逗号表达式

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?


代码2
if (a =b + 1, c=a / 2, d > 0)


代码3
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}

2.下标引用

[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值。

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9

3. 函数调用

( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}

4. 结构成员

访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名

#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
}

四:分支语句和循环语句

1. 什么是语句?

C语言中由一个分号 ; 隔开的就是一条语句。 比如:

printf("hehe");
1+2;

2. 分支语句(选择结构)

2.1 if语句

顾名思义,就是在两者或者多者之间选择一个或者说选择多个!

2.1.1 那if语句的语法结构是怎么样的呢?
if(表达式)
语句;
如果表达式结果为真,则执行下面的语句,否则直接跳出;

if(表达式)
语句1;
else
语句2;
如果表达式为真则执行语句1,否则执行语句2//多分支
if(表达式1)
语句1;
else if(表达式2)
语句2;
else
语句3;
如果表达式1为真则执行语句1,如果表达式2为真,则执行语句2,否则执行语句3
2.1.2那么在C语言之中如何表示真假呢?
          0表示假, 非0表示真
2.1.3 多条语句代码块的使用

如果条件成立,要执行的语句较多,那么应该合理的去使用代码块进行编写

#include <stdio.h>
int main()
{
if(表达式)
{
语句列表1}
else
{
语句列表2}
return 0;
} 

这里所提到的 { }就是一个代码块,平时我们在适当的代码之中使用 { }可以使得我们的代码逻辑更加的清晰,除此之外从一开始养成一个代码风格也是非常的重要的。

不适用 { } 的代码

#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
return 0;
}

使用 { }的代码块

#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)
{
if(b == 2)
printf("hehe\n");
}
else
{
printf("haha\n");
}
return 0;
}

两者的比较可以让我们非常容易的看出使用代码块的优势,所以刚刚学习C语言的朋友们,要尽可能地养成一个良好的代码风格和书写代码的习惯。

  1. 代码演示
#include <stdio.h>
int main()
{
int coding = 0;
printf("你会去敲代码吗?(选择1 or 0):>");
scanf("%d", &coding);
if(coding == 1)
{
prinf("坚持,你会有好offer\n");
}
else
{
printf("放弃,回家卖红薯\n");
}
return 0;
} 
  1. 多分支演示
#include <stdio.h>
int main()
{
int coding = 0;
printf("你会去敲代码吗?(选择1 or 0),如果家里很多房,选择 -1:>");
scanf("%d", &coding);
if(coding == 1)
{
prinf("坚持,你会有好offer\n");
}
else if(coding == 0)
{
printf("放弃,回家卖红薯\n");
}
else if(coding == -1)
{
printf("呵呵\n");
}
return 0;
}
  1. if书写形式的对比
//代码1
if (condition)
return x;
return y;

//代码2
if(condition)
{
return x;
}
else
{
return y;
}

//代码3
int num = 1;
if(num = 5)
{
printf("hehe\n");
}

//代码4
int num = 1;
if(5 == num)
{
printf("hehe\n");
}
可以清晰的看出来,代码2和代码4 是更好的,逻辑也是更加清晰不容易出错的。

4.练习题

1.判断一个数是否为奇数
2.输出1-100之间的奇数

2.2 switch语句

2.2.1 switch语句也是一种分支语句

常常用于多分支的情况,比如:

输入1,输出星期一
输入2,输出星期二
输入3,输出星期三
输入4,输出星期四
输入5,输出星期五
输入6,输出星期六
输入7,输出星期七

如果我们使用if ... else if ...else if...的话形式特别的复杂, 因此我们就需要使用不一样的语法形式去进行描述,也就是我们说道的switch语句。

switch(整型表达式)
{
语句项;
}
2.2.2 switch之中的语句项

switch之中的语句项又是什么呢?

是一些case语句:
如下:
case 整形常量表达式:语句;
2.2.3 break语句

在switch语句之中,我们是无法直接实现分支的,所以正确的switch语句是需要搭配break使用才能够真正的实现分支,break语句的实际效果是把语句列表划分为不同的部分,正确的代码如下:

#include <stdio.h>
int main()
{
int day = 0;
switch(day)
{
case 1printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("星期天\n");
break;
}
return 0;

如果我们的需求改变,要求输入1-5的时候输出“werkday”,而输入6-7的时候输出的是“weedend”,代码则变成了这样:

#include <stdio.h>
//switch代码演示
int main()
{
int day = 0;
switch(day)
{
case 1case 2:
case 3:
case 4:
case 5:
printf("weekday\n");
break;
case 6:
case 7:
printf("weekend\n");
break;
}
return 0;
}
2.2.4 default子句

如果表达的值域所有的case标签的值都不匹配怎么办? 其实也没什么 ----所有的语句都被跳过而已。 程序并
不会终止,也不会报错,因为这种情况在C中并不认为适合错误。 但是,如果你并不想忽略不匹配所有标签
的表达式的值时该怎么办呢? 你可以在语句列表中增加一条default子句,把下面的标签 default: 写在任何
一个case标签可以出现的位置。当 switch表达式的值并不匹配所有case标签的值时,这个default子句后面的
语句就会执行。所以,每个switch语句中只能出现一条default子句。 但是它可以出现在语句列表的任何位
置,而且语句流会像贯穿一个case标签一样贯穿default子句。

2.2.5 编辑好习惯!!!
  1. 在最后一个 case 语句的后面加上一条 break语句。 (之所以这么写是可以避免出现在以前的最后一个 case语句后面忘了添加 break语句)。
  2. 在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。

练习题

#include <stdio.h>
int main()
{
int n = 1;
int m = 2;
switch (n)
{
case 1:
m++;
case 2:
n++;
case 3:
switch (n)
{//switch允许嵌套使用
case 1:
n++;
case 2:
m++;
n++;
break;
}
case 4:
m++;
break;
default:
break;
}
printf("m = %d, n = %d\n", m, n);
return 0;

3. 循环语句

有些事必须一直的去做,比如我们的学习,只有不断的学习财能更好的认知到自己的无知!

C语言中如何实现循环呢?

3. 1 while语句

当条件满足的情况下,if语句后的语句执行,否则不执行。但是这个语句只会执行一次。
但是我们发现生活中很多的实际的例子是:同一件事情我们需要完成很多次。
那我们怎么做呢? C语言中给我们引入了:while语句,可以实现循环.

//while 语法结构
while(表达式)
循环语句;

比如我们要在屏幕上打印1-10 的数字:

#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
printf("%d ", i);
i = i+1;
}
return 0;
}

通过这个代码我们可以更好的了解while语句的基本语法

//while循环的实例
#include <stdio.h>
int main()
{
printf("努力学习\n");
int line = 0;
while(line<=20000)
{
line++;
printf("我要继续努力敲代码\n");
}
if(line>20000)
printf("赢取白富美\n");
return 0;
}
3.1.1while语句中的break
//break 代码实例
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
if(i == 5)
break;
printf("%d ", i);
i = i+1;
}
return 0;
}

这个代码的输出结果是什么?试试看你能否选对

A :1 2 3 4
B :1 2 3 4 5
C :1 2 3 4 5 6 7 8 9 10
D :1 2 3 4 6 7 8 9 10
正确的答案是 A

总结:其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。
所以:while中的break是用于永
久终止循环的。

3.1.2while语句中的continue
//continue 代码实例1
#include <stdio.h>
int main()
{
	int i = 1;
	while(i<=10)
	{
		if(i == 5)
		continue;
		printf("%d ", i);
		i = i+1;
	}
return 0;
}

输出选择
这里的输出又是什么呢?

//continue 代码实例2
#include <stdio.h>
int main()
{
int i = 1;
while(i<=10)
{
i = i+1;
if(i == 5)
continue;
printf("%d ", i);
}
return 0;
}

这里代码输出的结果是什么?

A :1 2 3 4
B :1 2 3 4 5
C :1 2 3 4 5 6 7 8 9 10
D :1 2 3 4 6 7 8 9 10
E :2 3 4 6 7 8 9 10

总结:continue在while循环中的作用就是:continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while
语句的判断部分。进行下一次循环的入口判断。
再补充几个代码,这都是什么意思呢?

//代码1
#include <stdio.h>
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
//代码2
#include <stdio.h>
int main()
{
while ((ch = getchar()) != EOF)
{
if (ch <0|| ch >9)
continue;
putchar(ch);
}
return 0;

break和continue知识点总结
break:跳出当前的循环语句,结束整个循环。
continue:结束这一次循环,直接进入下一次的循环。

3.2 for语句

3.2.1 for语法
for(表达式1;表达式2;表达式3)
循环语句;

表达式1 表达式1为初始化部分,用于初始化循环变量的。 表达式2 表达式2为条件判断部分,用于判断循环时候终止。 表达式3 表达式3为调整部分,用于循环条件的调整。

实际问题解决:使用for循环,在屏幕上打印1-10数字
#include <stdio.h>
int main()
{
int i = 0;
//for(i=1/*初始化*/; i<=10/*判断部分*/; i++/*调整部分*/)
for(i=1; i<=10; i++)
{
printf("%d ", i);
}
return 0;
3.2.1 for循环和while循环的对比
int i = 0;
//实现相同的功能,使用while
i=1;//初始化部分
while(i<=10)//判断部分
{
printf("hehe\n");
i = i+1;//调整部分
}
//实现相同的功能,使用while
for(i=1; i<=10; i++)
{
printf("hehe\n");

可以发现在while循环中依然存在循环的三个必须条件,但是由于风格的问题使得三个部分很可能偏离较远,这样查找修改就不够集中和方便。所以,for循环的风格更胜一筹。 for循环使用的频率也最高。

3.2.3 for循环之中的break和continue;

我们发现在for循环中也可以出现break和continue,他们的意义和在while循环中是一样的。 但是还是有些差异:

  1. While中的continue接下来所执行的是判定循环的条件
  2. for中的continue接下来所执行的是表达式3,然后再是表达式2;
//代码1
#include <stdio.h>
int main()
{
int i = 0;
for(i=1; i<=10; i++)
{
if(i == 5)
break;
printf("%d ",i);
}
return 0;
}
//代码2
#include <stdio.h>
int main()
{
int i = 0;
for(i=1; i<=10; i++)
{
if(i == 5)
continue;
printf("%d ",i);
}
return 0;
}
3.2.4 for语句的循环控制变量

一些建议:

1. 不可在for 循环体内修改循环变量,防止 for 循环失去控制。
2. 建议for语句的循环控制变量的取值采用“半开半闭区间”写法。


int i = 0;
//半开半闭的写法
for(i=0; i<10; i++)
{}
//两边都是闭区间
for(i=0; i<=9; i++)
{}

一些for循环的变种

#include <stdio.h>
int main()
{
//变种1
for(;;)
{
printf("hehe\n");
}
//变种2
int x, y;
for (x = 0, y = 0; x<2, y<5; ++x, y++)
{
printf("hehe\n");
}
return 0;
}
3.2.5 练习题和参看代码
  1. 编写代码,演示多个字符从两端移动,向中间汇聚。
  2. 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示
    登录成,如果三次均输入错误,则退出程序
练习题参考代码:
//代码1
#include <stdio.h>
int main()
{
char arr1[] = "welcome to bit...";
char arr2[] = "#################";
int left = 0;
int right = strlen(arr1)-1;
printf("%s\n", arr2);
//while循环实现
while(left<=right)
{
Sleep(1000);
arr2[left] = arr1[left];
arr2[right] = arr1[right];
left++;
right--;
printf("%s\n", arr2);
}
//for循环实现
for (left=0, right=strlen(src)-1;
left <= right;
left++, right--)
{
Sleep(1000);
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf( "%s\n", target);
}
retutn 0;
}
//代码2
int main()
{
char psw[10] = "" ;
int i = 0;
int j = 0;
for (i = 0; i < 3 ; ++i)
{
printf( "please input:");
scanf("%s", psw);
if (strcmp(psw, "password" ) == 0)
break;
}
if (i == 3)
printf("exit\n");
else
printf( "log in\n");
}
do
循环语句;
while(表达式

重点面试题:

//请问循环要循环多少次?
#include <stdio.h>
int main()
{
int i = 0;
int k = 0;
for(i =0,k=0; k=0; i++,k++)
k++;
return 0;
}

3.3 do … while语句

3.3.1 do语句的语法
do
循环语句;
while(表达式);
3.3.2 do语句的特点

循环至少执行一次,使用的场景有限,所以不是经常使用。

#include <stdio.h>
int main()
{
int i = 10;
do
{
printf("%d\n", i);
}while(i<10);
return 0;
}

3.3.4 重点算法实现
  1. 在这里要给大家讲的一个知识点就是折半查找算法:
    比如我买了一双鞋,你好奇问我多少钱,我说不超过300元。你还是好奇,你想知道到底多少,我就让你猜,你会怎么猜?
    答案:你每次猜中间数。
    代码实现:
实现在主函数内:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int left = 0;
int right = sizeof(arr)/sizeof(arr[0])-1;
int key = 7;
int mid = 0;
while(left<=right)
{
mid = (left+right)/2;
if(arr[mid]>key)
{
right = mid-1;
}
else if(arr[mid] < key)
{
left = mid+1;
}
else
break;
}
if(left <= right)
printf("找到了,下标是%d\n", mid);
else
printf("找不到\n");
}
  1. 如果实现一个二分查找函数:
int bin_search(int arr[], int left, int right, int key)
{
	int mid = 0;
	while(left<=right)
	{
		mid = (left+right)>>1;
		if(arr[mid]>key)
		{
			right = mid-1;
		}
		else if(arr[mid] < key)
		{
			left = mid+1;
		}
		else
			return mid;//找到了,返回下标
		}
	return -1;//找不到
  1. 猜数字游戏的实现(小时候是不是经常玩?)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void menu()
{
printf("**********************************\n");
printf("*********** 1.play **********\n");
printf("*********** 0.exit **********\n");
printf("**********************************\n");
}
//TDD-测试驱动开发。
//RAND_MAX--rand函数能返回随机数的最大值。
void game()
{
int random_num = rand()%100+1;
int input = 0;
while(1)
{
printf("请输入猜的数字>:");
scanf("%d", &input);
if(input > random_num)
{
printf("猜大了\n");
}
else if(input < random_num)
{
printf("猜小了\n");
}
else
{
printf("恭喜你,猜对了\n");
break;
}
}
}
int main()
{
int input = 0;
srand((unsigned)time(NULL));
do
{
menu();
printf("请选择>:");
scanf("%d", &input);
switch(input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("选择错误,请重新输入!\n");
break;
}
}while(input);
return 0;
}
3.3.5 练习题
  1. 计算 n的阶乘。
  2. 计算 1!+2!+3!+……+10!
  3. 在一个有序数组中查找具体的某个数字n。 编写int binsearch(int x, int v[], int n); 功能:在v[0]<=v[1]
    <=v[2]<= ….<=v[n-1]的数组中查找x。

第叁部分:

未完待续(先收藏起来之后慢慢看)

发布了19 篇原创文章 · 获赞 20 · 访问量 2188

猜你喜欢

转载自blog.csdn.net/Luckily0818/article/details/104402894