C语言复习第1章 基础语法

目录

一、概述及前置知识

1.1 什么是集成开发环境

IDE集成开发环境 编辑器 编译器 链接器 调试器
在这里插入图片描述

1.2 main函数

  • 在一个工程(项目)下 可以有多个.c文件 但是只能有一个main函数
  • C语言中必须有主函数 而且有且仅有一个主函数
    在这里插入图片描述

1.3 单位

在这里插入图片描述

1.4 注释

  • 预处理阶段 被注释掉的代码就删掉了
  • C语言的风格不能嵌套注释
    在这里插入图片描述

1.5 函数简介

  • ( )是个操作符 函数调用操作符
  • 函数的特点就是简化代码 代码复用
    在这里插入图片描述
    在这里插入图片描述

1.6 C语言中真和假的概念

  • 0=假
  • 非0(!0)=真 所以-1也是真
  • !假=真

1.7 C语言的内存分区

在这里插入图片描述

  • 下图栈区上的函数参数指的是形参
  • 静态区创建的变量(全局变量/静态变量)不初始化 默认是0
  • 但是局部变量 不初始化 放的是随机值(而且直接用的话 VS直接报错说未定义)
    在这里插入图片描述

1.8 EOF-文件结束标志

  • EOF本质是-1
    在这里插入图片描述
  • 成功读取到2个整数 scanf就返回2
    在这里插入图片描述
  • 正常情况输入Ctrl+Z scanf就会读取失败 但是VS有一个bug 要连按三次
  • 这样的话 第一个100成功读取 第二个失败 返回1
    在这里插入图片描述

1.9 头文件一般放什么内容

  • 而且一般include专门包含头文件的 不要去包含.c文件 这种牛角尖不要钻 没有意义
  • 写代码还是要规范!! 不要自找麻烦!!
    在这里插入图片描述

1.10 栈:从高用到低 数组:下标越大 地址越高

这个代码出现的错误 本身就依赖环境(VS2019 x86)
比如在VC6.0 i和arr之间就没有多余的空间
在gcc i和arr之间有一个整型空间
或者把x86改成x64 效果也会不一样
主要是学会调试和栈的使用方式

  • 按照函数栈帧那一节的画法 下面画成高地址 比较好理解 因为高地址先被使用 先入栈
  • 先使用高地址 再使用低地址
  • 数组下标越大 地址越高
  • 那么在这个环境先创建arr数组 再创建i 虽然还是越界了 但避免了死循环

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.11 断言assert(assert.h)

下图的if有两个问题:

  1. 直接return 是逃避问题 没有明确指出问题 程序员难以排查
  2. 不管是release还是debug if判断都会存在(但是这种判断不应该存在于release版本才对)

在这里插入图片描述

assert断言指针会更有效
assert会告诉你准确的错误信息
assert主要可以帮助程序员debug 所以release版本中assert就会被优化掉
在这里插入图片描述
在这里插入图片描述

1.12 release和debug

  • Debug通常称为调试版本 它包含调试信息 并且不作任何优化 便于程序员调试程序
  • Release称为发布版本 它往往是进行了各种优化 使得程序在代码大小和运行速度上都是最优的 以便用户很好地使用

1.10的案例 其实在release版本下运行就不会死循环
i的地址居然比较低 所以arr[i]就算越界 也不会影响到i
因为数组下标越大 地址越高
在这里插入图片描述

1.13 编程常见的错误

编译型

直接看错误提示信息 解决问题 编译都编译不过去的
相对来说简单 (语法错误)
双击你的报错 会出现一个黑色箭头
在这里插入图片描述

链接型

看错误提示信息 一般是标识符名不存在或者拼写错误
假如忘记include头文件 也会报这个错误
这种LNK的就是
可惜报错信息没啥用 只能自己Ctrl+F找找看了
在这里插入图片描述

运行时

借助调试 逐步定位问题 最难搞
需要经验 需要实践 不要畏惧 冲冲冲

二、数据类型

2.1 八大类型的大小

  • sizeof 是一个操作符 而不是函数 计算结果的单位是字节
  • long long是C99标准加入的数据类型
  • C语言标准规定:sizeof(long)≥sizeof(int) 即可 当前编译器取的就是等于
  • 为什么需要丰富的类型:为了描述实际问题 本身就需要整数 小数 字符…
  • 为什么同一种类型又分好几类:如果描述年龄 short就足够了 适当的类型给适当的东西用 更加节省空间
    在这里插入图片描述
    在这里插入图片描述

2.2 默认浮点数为double类型

在这里插入图片描述

  • 浮点数无法精确存储 详见后面的IEE754的规则
    在这里插入图片描述

2.3 占位符

  • 更多细节见本文的6.3

  • sizeof的返回值其实是size_t

  • size_t在64位的环境下是64位的整型 建议用 %zd或者%zu来打印

  • size_t在32位的环境下是32位的整型 用 %d %u %zd %ud打印都没问题
    在这里插入图片描述

  • %u是打印无符号数的 也就是打印unsigned int的数据类型(他打印的应该是32位的无符号整型)

  • 反正sizeof就用%zd; unsigned int就用%u 要根据编译器的提示灵活运用
    在这里插入图片描述

2.4 如何表示八/十进制

● 这个B的值 是10 8+2(八进制)
● 16进制就是int b = 0x55 (= 85 = 1010 1010)
在这里插入图片描述

2.5 C语言中 表达式有两个属性

在这里插入图片描述

2.6 C语言标准没有规定char类型的符号

在这里插入图片描述

2.7 C99引入布尔类型

在这里插入图片描述

在这里插入图片描述

  • 其实本质还是0或者1
    在这里插入图片描述

三、变量与常量

3.1 变量的命名规则

在这里插入图片描述

3.2 变量最好要初始化

  • vs2019还是比较严格的 所以还是建议务必要初始化
    在这里插入图片描述

3.3 变量的分类

  • 主要分为局部变量和全局变量
  • 区分的标准:看{} 在{}内部就是局部 在外部就是全局变量
    在这里插入图片描述
  • 全局变量不安全 谁都可以用 要尽可能的少用
    在这里插入图片描述
    在这里插入图片描述
  • 局部和全局变量名冲突的时候 局部优先 下面代码的结果为1
    在这里插入图片描述

3.4 变量的作用域

概念

在这里插入图片描述

局部变量

  • 局部变量的作用域是变量所在的局部范围
    在这里插入图片描述

  • 局部变量如果先使用 后定义 需要先声明

  • 这里VS的比较严格 直接报错而不是报警告(从前往后扫 没扫到g_a就使用了)
    在这里插入图片描述

  • 声明一下即可

  • 使用变量: 先声明再使用; 什么时候使用,什么时候定义

  • 如果定义就直接定义在前面的话 就不需要单独声明了(这样就更规范)
    在这里插入图片描述

全局变量

  • 全局变量的作用域是整个工程 (因为只要合理声明 全局变量在当前项目下都可以使用)
    在这里插入图片描述

  • 在另一个.c 文件里定义的全局变量 需要 extern 声明外部符号 才可以使用(跨文件使用全局变量 需要 extern)
    在这里插入图片描述
    在这里插入图片描述

3.5 变量的生命周期

在这里插入图片描述

  • 局部变量a的生命周期:进入局部范围生命周期开始 出了局部范围生命周期结束
  • 进入局部范围:申请内存 创建变量 开始
  • 出了局部范围:消亡 归还内存给操作系统 a已经不可以再使用 不是真的销毁了
    在这里插入图片描述
  • 只要程序还活着(程序还没结束) 全局变量就可以使用
  • 全局变量在整个main函数里都可以使用 而主函数的生命周期就是整个程序的生命周期
  • 进入主函数 程序的生命周期开始 出了主函数 程序就结束了 所以可以说全局变量的生命周期 就是整个程序的生命周期

3.6 常量

字面常量(注意字符串字面常量)

  • 字符串常量本身作为一个表达式 赋给变量的时候 就是把首字符的地址给变量
  • int a = 3; char* p = “abc”;(这里p指向的字符串常量 是不可以被修改的)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

const 修饰的常变量

  • 仿佛让一个变量具有了常量的属性 但他 本质上还是一个变量 出了作用域也会销毁
    在这里插入图片描述

  • 就算我把下图的 n 换成 const修饰的 n 仍然报错 可以证明 n 还是个变量

  • 只不过具有了常量不可修改的属性(语法层面上的常量 不能被修改)

  • 当我希望一个变量不可以被修改–>用const修饰的常变量
    在这里插入图片描述
    在这里插入图片描述

#define 定义的标识符常量

  • M确实是全局的 但是不能说他是全局变量 M就是一个#define标识符常量
    在这里插入图片描述

枚举常量

  • 这三个可能的取值 (R G B)就是枚举常量 表示Color所有的可能取值(也就是下图c可能的取值就是R G B)
  • 所以BLUE就可以定义数组的大小
  • 把 enum Color 看成 int 这样的一个类型 只不过是自定义类型
    在这里插入图片描述
    在这里插入图片描述

3.7 关于const

字符串常量 都用const修饰更规范

  • 既然p指向的字符串字面常量是不可以被修改的 那直接显式的把指针p用const修饰 这样更好
  • 不用const修饰 程序直接崩了 用const修饰 就会报一个编译时的错误 更规范 更安全
    在这里插入图片描述

const修饰指针变量(分左右)

下图有一个漏洞 这里的常变量num 可以通过指针修改(绕过了const?)

在这里插入图片描述

const放在*左边的时候(const int *p = &num 或者 int const *p = &num)
const限制的是*p 即p所指向的内容 不可以再通过*p解引用被修改
注意 是不能通过*p解引用这种方式去修改num
假如指针p指向的是num本身没有被const修饰 还是可以把其它值赋给num的
在这里插入图片描述

指针变量p本身还是可以修改的

在这里插入图片描述


const在*右边 (int * const p)
const限制的是p指针变量p本身不可以被修改 不能再指向别人
指针变量p指向的内容 还是可以通过*p解引用被修改
在这里插入图片描述


下图这种情况就是 p彻底被限制了 p不能改 *p也改不了
所以 当我们希望一个变量比如说num 不能被修改:
首先num本身要用const修饰
其次当把num交给一个指针的时候 最好把const放在*的左边 这样也无法通过指针来修改num
在这里插入图片描述


总结来说 const修饰指针变量 修饰的就是const右边的那一坨东西
要么是:指针本身不可以修改(不可以指向别人)
要么是:不可以通过指针(解引用)修改指针所指向的内容
在这里插入图片描述

擅用const的一个实际意义

  • 在模拟实现strcpy的时候 把src(源字符串)加一个const修饰 是最规范的 如果硬要改 编译都通不过 不会产生运行时错误 程序不会直接崩掉
  • 假如下面传上来的是 char *p = "accc"的p 本身字符串常量就不能被修改 那就直接加个const 更加明确
    • 或者说 不管传进来的是字符数组还是字符常量 肯定不希望被拷贝的字符串被修改 那就直接用const char *src 修饰 而且是放在左边 const修饰*src src指向的内容不能被修改了 增加了代码的健壮性
  • const能让一个运行时错误(错了只能自己慢慢debug) 变成一个编译时错误
    在这里插入图片描述

3.8 变量的声明和定义

  • 定义变量的时候 尽量要赋初始值(初始化) int a = 0;(不赋值 可能会给一个随机值 也可能编译都不通过 比如 VS2022)

  • 声明:int a; 只需要告诉数据类型+变量名

  • 定义本身也是一种特殊的声明 变量必须先声明(定义)再使用

  • 经过测试发现:在VS2022里 全局变量定义的时候不赋初试值 会有默认值(int就是0) 不会报错 但是局部变量定义不赋初值的话 就直接报错了
    在这里插入图片描述

  • 如果定义在后面 使用在前面 就要声明一下

  • 总而言之 先定义再使用 定义的时候记得初始化(代码更规范 可控)
    在这里插入图片描述

  • 对于全局变量(不初始化有默认值)来说 有如下规则(老师提了一下…我感觉有点抠字眼了)
    在这里插入图片描述

3.9 局部/全局/静态变量的默认值

  • 静态区创建的变量(全局变量or静态变量)不初始化 默认是0
  • 但是局部变量 不初始化 放的是随机值 建议都初始化一下
    在这里插入图片描述
    在这里插入图片描述

3.10 变量的初始化和赋值

  • 赋值要从右往左读 读成把20赋给a
    在这里插入图片描述

3.11 函数是没有生命周期的概念的

  • 生命周期是针对变量来说的
  • 函数就是一坨代码 不管调不调用它都在 只不过存在一个能不能调用的问题 是没有生命周期的概念的
  • 函数是一段代码 一段二进制的东西 如果函数没有被调用 他是不会向内存申请空间的 只有函数被调用 才会开辟函数栈帧
  • static其实就是影响了函数能够调用的范围

四、字符串

4.1 概念

在这里插入图片描述

4.2 两种字符数组

  • ‘\0’ 作为字符串的结束标志 不算作字符串的内容(strlen的时候不会算\0)
  • { }定义什么就是什么;"XXX"自带一个\0
  • 一个字符串也可以放到一个字符数组里去
    在这里插入图片描述

4.3 %s打印字符串(数组名/指针变量/起始地址作为参数)

  • %s打印字符串 从传给printf的指针变量(看做起始地址)开始 打印到 第一个’\0’ 就停止
  • strcpy返回的是arr1的首地址 然后用ret接该首地址
    在这里插入图片描述
    在这里插入图片描述
  • 对于{ }的方式 这样写就正常了
    在这里插入图片描述

4.4 strlen()求字符串长度

  • strlen()计算字符串长度 只计算 \0 之前的长度
  • 因为\0不算字符串的内容 所以用strlen()求长度的时候 也不会算\0(遇到第一个\0就不再往后算)
  • 所以len1的结果是个随机值 未知
    在这里插入图片描述

4.5 char *p = “hello”字符数组的区别

  • char *p = “hello” 和 char arr[10]=“hello” 是不一样的概念
  • p是一个指向字符串常量的指针不可以通过解引用p来修改p所指向的字符串常量的
    在这里插入图片描述
    在这里插入图片描述
  • *p是一个指针(建议用const修饰*p) 指向字符串常量;arr是一个字符数组
  • 字符串字面常量存在只读内存中;字符数组存在栈区
    在这里插入图片描述

4.6 sizeof和strlen()

  • strlen( )是库函数 是计算字符串长度的 统计的是字符串中第一个\0之前出现的字符个数 他仅仅针对于字符串
  • sizeof是一个单目操作符(只有一个操作数)
    ● sizeof(数据类型) 数据类型占内存大小
    ● sizeof(变量名) 变量所占内存大小
  • 注意:sizeof(数组名)算的是整个数组的大小 此时的数组名表示整个数组 而不是首元素地址
    在这里插入图片描述

在这里插入图片描述

4.7 ’ ’ 与 " "(存在问题)

  • 字符一定要用’ '单引号引起来 而且里面只能有一个字符
  • " "里可以引≥1个字符 “a” 这也是一个字符串 只不过该字符串只有一个字符
  • 这个问题应该和char的范围有关系 忘记后面在哪 看到记得补上
    在这里插入图片描述

4.8 直接printf(“hello”) 或者 printf(地址)

来看一下函数原型 这是没问题的
在这里插入图片描述

字符串常量如果作为一个表达式 他的值本来就是首字符地址
printf(“hello”)本来就相当于printf(&h)
也可以直接写成printf(str)
因为这里只需要打印一个字符串
常量字符串 放在表达式里面 放的其实是首字符的地址
注意:一模一样的字符串常量在只读区只需要存一份
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.9 sizeof和strlen()求字符串(数组元素个数)长度

1.不能用strlen求int arr[]的长度 strlen是专门给字符串用的 int类型的数组只能用sizeof计算
在这里插入图片描述
2.求字符串的长度直接就用strlen()函数 下图用sizeof求是错的 因为它无法考虑字符串结束标志
在这里插入图片描述
3.总结来说
strlen()函数时专门用来求字符串(有\0作为结束标志)的长度的 即有效字符个数
而sizeof单目操作符可以通过计算来求出各种类型数组的元素个数

五、转义字符与ASCII码表

5.1 转义字符是什么

  • 转义字符 本质还是字符–>%c 也满足char的范围 也要用单引号’ '引起来
    在这里插入图片描述

5.2 常见的转义字符

  • \?防止在书写连续多个?的时候 ?被解析成三字母词

  • \\防止\被解析成一个转移序列符号
    在这里插入图片描述

  • \0也是一个转义字符 表示空字符(NULL)
    在这里插入图片描述

  • 退格符
    在这里插入图片描述

5.3 三字母词(了解即可)

  • 支持三字母词的编译器下 ??) 被解析成 ]
    在这里插入图片描述
  • 为了避免这种情况 就可以用\? 让?就是一个? 而不是三字母词的一份子
    在这里插入图片描述

在这里插入图片描述

5.4 打印c:\code\test.c(\+任意字母是什么效果)

在这里插入图片描述
在这里插入图片描述

  • 我的推论是不管C语言有没有规定过某个转义字符 \总是会跟他最近的一个字符结合起来 所以想打印\ 无脑用\\就行
    在这里插入图片描述
    在这里插入图片描述

5.5 \ddd与\xdd 0开头与0x开头

  • 注意了 \ddd和\xdd这俩本质都是字符 是char
  • \ddd表示1到3个八进制数字 如:\130(字符X)
  • \xdd表示2个十六进制数字 如:\x30(字符0)
  • 八进制073 = 十进制59 对应到ASCII码表就是字符;
    在这里插入图片描述
  • 意思是\后面跟的1到3位0~7的数字 都看做是八进制数字 他们总体只算做一个字符
    在这里插入图片描述
  • 如果是字面上去表示8/16进制的时候 八进制是0开头:071 十六进制是0x开头:0x23
    在这里插入图片描述

5.6 ASCII码表

  • ?#$a....这些字符或者符号 比较特殊 而内存里存的都是二进制 怎么把这些符号存到内存里?--->给这些符号编号 再把编号对应的二进制存进去--->ASCII编码
  • ‘a’—>97
  • ‘A’—>65
  • ‘0’—>48
  • 小写a比大写A大了32 A+32=a
    在这里插入图片描述

5.7 printf(“%d”,‘\777’); 为什么会报错(char的范围) 仍存在问题

  • 首先 不管用%d还是%c 去打印转义字符’\777’都会报错 因为转义字符他本质上也是个字符 也就是char 范围要么是-128~127(有符号) 要么是0~255(无符号) \777这个字符对应十进制511 本身就太大了!!!

  • 而ASCII码表能表示128个字符(0~127) 在这个范围用%c打印 是可以查表看看打印啥字符的
    在这里插入图片描述

  • %c打印字符的时候 如果这个字符不在ASICC码表的范围 他也能打印出奇怪的东西 就不再深究了

  • \200=128 但对于char来说 128=127+1= -128 后续章节会详述
    在这里插入图片描述

  • 仍存在问题 写完数据的存储 再回头
    在这里插入图片描述
    在这里插入图片描述

5.8 理解0 ‘0’ ‘\0’

  • 字符’a’—>97(ASCII 码值)

  • 字符’0’—>48(ASCII 码值)

  • 字符’\0’—>0(ASCII 码值)

  • 只不过字符\0这个转义字符 他就表示一个空字符 所以在ASCII码码值为0的地方是空白
    在这里插入图片描述

  • 所以"abc" = {‘a’,‘b’,‘c’,‘\0’} = {97,98,97,0}
    在这里插入图片描述

六、printf与scanf

6.1 scanf的返回值及如何多组输入

  • scanf是C语言的 scanf_s是VS特有的

  • scanf的返回值:实际读到数据的个数 如果读取数据失败 就返回 EOF
    在这里插入图片描述

  • EOF(-1)—End Of File 文件结束标志(只不过在 scanf 这里函数里用到了)

  • 如下图 就可以实现多组输入
    在这里插入图片描述

  • 假设明确知道需要读取到几个数据 也可以用==num来判断
    在这里插入图片描述

  • EOF的本质其实是-1 如果读取失败或者一个都没读到 就返回EOF
    在这里插入图片描述
    在这里插入图片描述

6.2 scanf的格式要注意

  • scanf一定注意格式 要一模一样
    在这里插入图片描述
  • 要不然 明显就有bug了
    在这里插入图片描述

6.3 scanf和printf的占位符

  • long long - scanf和printf都是%lld
  • 对于scanf float是%f double是%lf 不能混淆
  • 在vs里 对对printf float肯定是%f double也可以写成%lf的 建议就统一float-%f double-%lf
    在这里插入图片描述
    在这里插入图片描述

6.4 printf打印怎么对齐

在这里插入图片描述

  • %2d 右对齐 不足2位 左边边补空格
  • 左对齐换成负数就行了
  • 左-右+
    在这里插入图片描述
  • %-2d 左对齐 右边补空格
    在这里插入图片描述

6.5 printf格式一定要匹配 避免不必要的问题

  • 这种奇怪的bug 完全是可以避免的

  • 格式一定要匹配 避免未定义行为!!!
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

6.6 scanf里不要有中文提示\n之类的

  • 写昏了头 居然写出这种代码了 还没反应过来…
  • scanf是严格根据" "里的格式来的 这么写 控制台输入也要是请输入:XX 很不合理 所以一般情况 scanf只有占位符+地址 然后去缓冲区读取数据
    在这里插入图片描述

6.7 scanf遇到空格或者\n就不继续读了

  • 详细介绍见本文的10.3

注意了:scanf拿字符串的时候 只拿到空格或者\n之前
遇到空格或者\n 就不拿了

6.8 scanf用%s读取字符串的时候 在恰当的时候会自己加上’\0’

注意我按回车键入之后的变化
在这里插入图片描述

自己加上了一个’\0’
我猜测这是scanf做的事 因为虽然"ff"自带\0 但是我键入ff回车的话 应该并没有自带\0
在这里插入图片描述

七、数组简介

7.1 数组的创建和初始化

在这里插入图片描述

  • 也可以不指定大小 但必须显式初始化根据后面的内容分配大小
    在这里插入图片描述

7.2 数组不完全初始化的默认值

  • 注意:char类型数组不完全初始化默认值是 ‘\0’ 而不是’0’在这里插入图片描述

7.3 C99标准支持变长数组

  • 定义时:arr[ x ] 一般来说 x 肯定是常量
  • 访问时:arr[ x ] x 才可以是变量
    在这里插入图片描述
  • 如果编译器支持C99 x就可以写成变量(下图是利用gcc测试变长数组)
  • 但是如果定义了变长数组 就绝对不可以初始化(创建的同时赋值 就是初始化)
  • 这里也有一个误区:变长数组不是说数组的大小能随时变化 "变"仅仅体现在可以用变量定义数组的元素个数 一旦那个变量的值确定了 数组的元素个数也就确定了
    在这里插入图片描述

7.4 如何访问数组元素

  • 下标默认从0开始
  • 定义数组的时候 [ ]里必须是常量 表示数组的元素个数
  • 但是访问数组元素的时候 [ ]里可以是变量 变量的值就是下标值
    在这里插入图片描述

7.5 数组名本身就是地址 %s打印的时候不需要再&

在这里插入图片描述

八、常见操作符

8.1 %与/

  • %取模(取余) 两边必须都是整数
    在这里插入图片描述

  • /号两端的操作数如果都是整数 执行的是整数除法 结果也是整数

  • /号两端如果至少有一个浮点数 就执行浮点数除法 结果也是浮点数

  • 注意(float)强转的优先级

  • (float)(10/4)答案是2.0->10/4整体被强转

  • (float)10/4答案是2.5->10先被强转
    在这里插入图片描述在这里插入图片描述

8.2 !逻辑反操作-单目操作符

  • 一般是在if里用 !真就是假
    在这里插入图片描述

8.3 强制类型转换-单目操作符

  • 强转( ) 也是一个单目操作符
  • 强转不会发生四舍五入 直接舍去了小数部分
    在这里插入图片描述

8.4 逻辑操作符&& ||

  • &&有假则假
  • || 有真就真
  • 他们只针对真和假
    在这里插入图片描述

8.5 易错:C语言中 不能使用连等判断

  • 先判断18<= 2 假的 结果为 0

  • 然后判断0<=36 成立 故打印青年 显然是错误的
    在这里插入图片描述

  • 正确逻辑
    在这里插入图片描述

8.6 条件操作符

  • 如果a>b
  • 就执行a = a - 1 :后的表达式就不执行
  • 并把 a = a - 1的结果 作为整个右边条件表达式的结果赋给m
    在这里插入图片描述
  • 如果1为真 就执行2但不执行3 且2的结果作为整个表达式的结果
  • 如果1为假 就执行3但不执行2 且3的结果作为整个表达式的结果
    在这里插入图片描述

8.7 sizeof-单目操作符

  • sizeof是操作符 不是函数
    在这里插入图片描述

8.8 ++ - - 单目操作符

  • a++是一个表达式 把表达式的值赋给b 由于后置++先使用再++的特点 表达式的值是a原来的值 也就是100
  • 最终a = 101 b = 100
    在这里插入图片描述

8.9 易错:==是判断 =是赋值

  • == >= 等等是关系操作符
  • =是赋值操作符
    在这里插入图片描述

8.10 下标引用操作符[ ]

  • 第一个[ ]不是操作符 他就是定义数组的语法
  • 第二个[ ]才是下标引用操作符
    在这里插入图片描述

九、常见关键字

9.1 C语言有哪些关键字

  • 关键字是不能作为变量名的
  • C语言规定好了关键字 用户是不可能自己造关键字的
    在这里插入图片描述

9.2 auto

  • 进{ }的时候 创建变量 出去的时候 就销毁了 (不是真销毁 是还给操作系统)
  • 即:局部变量:自动创建 自动销毁 所以又叫做自动变量(auto修饰的变量)
  • 既然所有局部变量都是这样的 那么 auto 后来就被省略了
    在这里插入图片描述

9.3 register寄存器

早期CPU处理的数据来自于内存,因为早期CPU计算/处理的速度不是非常快,内存的访问速度能跟得上CPU(配合的好)
但是后来随着发展,CPU的处理速度越来越快,存储设备的读写速度提升的却没有那么快,逐渐拉开差距,就采取下面这种方式,使得CPU的处理速度整体更高效
CPU每次都去寄存器拿数据 但是与此同时内存的数据都被载入到高速缓存 高速缓存的数据也在载入到寄存器

  • 寄存器是集成到CPU上的 和内存没有关系 寄存器是一块独立的存储空间
    在这里插入图片描述
  • 使用这个关键字 只是建议把a放进寄存器 到底放不放 取决于编译器
  • 假如a频繁大量的使用 把a放到寄存器 效率会高一点
  • 有时候编译器根据实际情况 不写register 它也会载入寄存器 所以在当前比较聪明的编译器下 register的意义也不是很大了
    在这里插入图片描述

9.4 typedf类型重命名

在这里插入图片描述
在这里插入图片描述

9.5 static

链接属性

  • 内部链接属性:只能在当前源文件内部使用
  • 外部链接属性:声明得当,可以跨文件使用
  • 局部变量是没有链接属性的 不管是不是被static修饰 局部变量都只能在自己的局部范围使用 所以static改变的是局部变量的生命周期 而非作用域
  • 全局变量和函数才谈论链接属性 没有static修饰 是具有外部链接属性的(自然也涵盖了内部链接属性) 一旦被static修饰 就只有内部链接属性了

修饰局部变量

  • 注意:这俩a都还是局部变量 作用域是没有改变的!!! 相当于是改变了生命周期
  • 作用域不会改变:static修饰的局部变量a 依然只能在test函数内部才能使用
  • 生命周期变长了:static修饰的局部变量a 出了他的作用域 并没有销毁 直到程序结束才销毁
  • 下图栈区说的函数参数一般指的是形参
    在这里插入图片描述
    在这里插入图片描述

不用static修饰的情况:

  • 每次调用test( ) 进入这个函数 创建局部变量 出去就销毁 所以这十次 每次都是重新创建的a 然后a++ 然后打印a 然后出去并且销毁
  • 这个a是在栈区的 是临时的 出了作用域就被释放了
    在这里插入图片描述

用static修饰的情况:

  • 这个a是在静态区的 静态区的变量在创建之后 直到程序结束才会释放 作用域不变 但是生命周期延长了
  • 第一次调用test()的时候创建的a 并没有被销毁
    在这里插入图片描述
  • 调试的时候 直接跳到56行执行++了 而如果没有static 每次都会执行int a = 0 重新创建a
    在这里插入图片描述

修饰全局变量

  • 如果只想自己独享某个全局变量–>就用static修饰该全局变量

  • 无static修饰的全局变量:全局变量本身具有外部链接属性 在A文件中定义的全局变量 在B文件中可以通过链接使用
    在这里插入图片描述
    在这里插入图片描述

  • static修饰的全局变量:只有内部链接属性 只能在该全局变量当前的源文件下使用

  • 即使用extern声明了 也无法使用
    在这里插入图片描述

  • static关键字会把全局变量的外部链接属性变成内部链接属性–>使得全局变量作用域变小
    在这里插入图片描述

修饰函数

  • 函数和全局变量的情况很类似 函数本身也是具有外部链接属性的

  • 如果只想自己独享某个函数–>就用static修饰该函数
    在这里插入图片描述
    在这里插入图片描述
    理解一下为啥要用extern声明一下?

  • 当编译test.c的时候( 如果没有extern int Add(int,int);) 会报警告:说Add未定义

  • 因为C语言的编译器 都是对这种.c文件单独分开编译的 单独编译test.c的时候 如果直接用了Add 但是Add其实是定义在add.c的 你在test.c里又不声明一下说已经有了Add这个函数 肯定不规范 编译器肯定也不认识Add函数 想不报警告 就要加上extern外部声明
    在这里插入图片描述

  • 被static修饰之后 就变成了内部链接属性了 使得该函数只能在自己所在的源文件内部使用 在其他源文件无法使用

  • 即使声明了该函数 也不能在其他文件使用

  • 其实就是限制了函数的作用域 改变了函数能够被调用的范围 仿佛把那个函数隔离了
    在这里插入图片描述

9.6 #define不是关键字 他是预处理指令

  • 这东西不是关键字!!! 他是用来定义符号和宏的
    在这里插入图片描述

  • 他可以定义标识符常量 这个M不是全局变量 但是可以看作一个全局的"符号"
    在这里插入图片描述

  • 或者定义宏 宏会直接被它的宏体替换 仅仅替换 不做任何其他操作

  • 符号和宏的区别就在于:宏是有参数的

  • 一般宏都是处理比较简单的逻辑 复杂的不建议用宏
    在这里插入图片描述

9.7 宏和函数的区别

  • 函数的参数有类型 宏没有
  • 函数有返回类型 宏没有
  • 函数{ }里是函数体 宏的宏体直接定义在后面
  • 函数处理复杂逻辑 宏处理简单逻辑
    在这里插入图片描述

十、getchar和putchar

10.1 getchar的功能和返回值

  • scanf 和 printf针对各种各样的类型的数据
  • 但是getchar 和 putchar是专门针对字符(char)的
  • getchar的返回值是:读取到的字符对应的ASCII码值 比如读取到A 就返回65
  • getchar如果读取失败 也会返回EOF
    在这里插入图片描述

在这里插入图片描述

10.2 putchar的功能和返回值

在这里插入图片描述

这就是二者最基本的使用
在这里插入图片描述

10.3 输入缓冲区的概念

平时用scanf或者getchar的时候 为什么光标在黑框框闪烁等你输入? **这是因为输入缓冲区里没有数据 所以只能等你再从键盘输入数据到输入缓冲区!**
在这里插入图片描述

下图要好好理解一下:其实我键盘输入A 然后按回车是执行了两次循环的
循环开始之后 发现输入缓冲区没有东西 只能闪烁等待键盘输入
当我输入A(也就是按了A 和 回车) 这个时候输入缓冲区里放的是A和\n
getchar先读取到A 返回值是65 != EOF(-1) 就进入循环体 执行putchar(‘A’)
然后又回到判断部分 这一次getchar读取到了\n 也!=EOF 所以又进入了循环体 并putchar(‘\n’) 所以有换行的效果
然后第三次来到判断部分 这才发现 输入缓冲区里已经没有东西了 再次闪烁等待键入
在这里插入图片描述

  • 那下面的代码 理解起来就很容易了
    在这里插入图片描述

10.4 getchar和scanf关于缓冲区的问题

键盘输入数据-->缓冲区 getchar和scanf 是从缓冲区里拿数据

下图的错因 就很显然了
刚开始执行 缓冲区没东西
所以scanf等待键盘输入东西进缓冲区 我输入123456 然后按回车
这个时候缓冲区有:123456\n
然后scanf就把123456这个字符串拿走了 并赋给password(因为是%s)
缓冲区还剩:\n
注意了:scanf拿字符串的时候 只拿到空格或者\n之前 遇到空格或者\n 就不拿了
继续执行 getchar发现缓冲区有东西:\n 他就不会闪烁光标等待键盘输入 而是直接把\n拿走 并赋给ch
然后发现ch != Y 故直接就打印:确认失败
在这里插入图片描述

在ch = getchar( )前面 再加一句getchar( ); 就可以解决问题
这个新的getchar( )把\n拿走了
然后执行到ch=getchar( ) 发现缓冲区没东西 就会闪烁光标等待输入
在这里插入图片描述

10.5 利用while清理缓冲区

进一步的 假如我输入的更逆天
不仅仅是字符串+回车
比如我输入 abc空格efg\n
那么scanf只读到abc(遇到空格或者\n都不读了) 后面一坨留在缓冲区了怎么清理?
方案是:用while清理(注意下图的循环 包括\n也清理了 因为是先清理 再判断的)
执行流程 假如输入abc efg\n:
scanf把abc拿走了 遇到空格就不继续读了
`
然后getchar拿走空格 返回对应的ACISS码值 !=\n 继续
拿走e 返回对应的ACISS码值 !=\n 继续
拿走f 返回对应的ACISS码值 !=\n 继续
拿走g 返回对应的ACISS码值 !=\n 继续
拿走\n(拿走了再判断的) 发现== ‘\n’ 循环结束
在这里插入图片描述

10.6 我的一个猜测-建议每次%c读取字符之前 都清理一下缓冲区

  • 这个没什么意义 仅仅是我的上机得到的猜测 没必要纠结 随便看看
  • 在这里插入图片描述

问题描述:
经过实践 我猜测

  1. 如果缓冲区里的东西不满足占位符比如%d的要求 他也会等待键入
  2. %d似乎把上次%s键入的\n也给拿走了 这也许是VS的优化
    在这里插入图片描述

那为什么%c不把marry留下的\n也拿走?
因为%c要的就是字符啊 他一看缓冲区还有一个\n 就是字符 都不等待键入 直接拿走了

在这里插入图片描述

  • 那也就是说 除了%c 其他的没必要过于操心
  • 如果有%c读取 就要当心缓冲区是不是干净了 在读取%c字符之前 最好用while循环清理一下缓冲区的内容
int main()
{
	char name[10] = "";
	int age = 10;
	double saly = 0.0;
	char gender = ' ';

	//读取
	printf("姓名:\n");
	scanf("%s", name);

	printf("年龄:\n");
	scanf("%d", &age);

	printf("工资:\n");
	scanf("%lf", &saly);
    //此前 都很正常
    //读取%c之前 就要当心缓冲区是不是干净了 建议用while清理一下
	while (getchar() != '\n')
	{
		;
	}
	//清理过后 再读取%c 就不会出错
	printf("性别:\n");
	scanf("%c", &gender);

	printf("%s %d %1f %c", name, age, saly, gender);
	return 0;
}

十一、其他

说明

这是平时钻的一些牛角尖 我认为没有太大意义 不需要过度纠结

这种语句放在函数内部

  • 给他加个大括号也不行 必须在函数内部 不然没机会执行 语法也是错的
  • 这tm谁想出来的
    在这里插入图片描述

变量才能赋值 表达式不能

  • 常量和变量比较的时候 建议把常量放在左边 这样就可以防止把==写成=而没发现了
  • 因为写if(10 = a) 就err了 然后就发现了 ==写成=了
  • 10 = a //err
  • a = 10 //ok
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值