C语言学习笔记(408版本-初级阶段)
一、基础阶段
1.C语言背景、环境安装、调试
背景
可移植:C语言
跨平台:Java、Python
C语言不是跨平台,因为C语言中,系统硬件操作的接口,windows、Linux不一样
408目前不涉及这些接口,掌握标准C够用
环境安装
下载使用社区版够用,目前推荐2019版,启动安装包之后如果下载失败,需要修改host
创建新项目
- 创建新项目->空项目
- 一个解决方案里可以包含多个项目,先不用勾选“解决方案名称和项目名称放同一目录”
- 右键源文件->添加->新建项
- 选C++,命名用.c后缀,如main.c(习惯);因为写成.c就用C语法编译,写成.cpp就用C++语法编译(更严格),初学者用.c即可
用printf输出个hello world
- 绿色按钮,启动程序
- 2012版需要添加pause,否则命令行弹框会一闪而过,2017/2019无需写就能自动暂停
- 暂停是visual自带功能,如果直接执行.exe文件则不会暂停
理解编译过程
- .c文件(编译)->.obj目标文件(链接)->.exe可执行文件
- .obj文件没有我们需要用的函数,而是将动态库的函数链接过来生成可执行文件
- 实际应用时要注意是编译错误还是链接错误
调试
- 把行号设置显示,方便定位问题
- 设置内存、监视窗口
- 通过调试,非常清楚内存的变化过程,才能理解C语言
2.数据类型、运算符与表达式
mac使用vs code
vscode安装插件,安装编译器,
编译运行,调试
数据类型
看笔记图:基本类型,构造类型,指针类型,空类型
C语言关键字(32个),写代码时不能用关键字命名
常量,""空字符串
变量
- 命名规范:字母、数字、下划线,第一个字符不能是数字
- 体验如何查看内存并调试
符号常量
- 不能被赋值
进制的转换
- 十进制转为二进制,不断的除2,余数倒着写上去
Scanf简单使用
- 不要使用微软的scanf_s,因为机试不用scanf_s
- 解决scanf编译报错问题,
#define _CRT_SECURE_NO_WARNINGS
3.进制讲解,浮点数,字符型
OJ使用
- 校招也会用来出题
- 不要有无用代码,比如printf请输入提示语之类的,否则会判题失误
进制变换
- 调试查看内容,用的就是十六进制,因为看内存很高效
- 二进制转成八进制、十六进制非常方便
- 英特尔CPU使用小端存储,所以低位在前
- 64位电脑的寻址寻址空间已经足够使用
4.混合运算,scanf标准输入
学C语言注重调试能力,注重计算机原理的解析,
字符常量,字符串常量
- 大小写字母转换:在内存中的数值相差32
- C语言没有字符串变量,而是用字符数组存储字符串;
- 单引号是字符型常量,双引号是字符串型常量
- 字符串常量:一个字符占一个字节,外加一个\0占一个字节作为结束符
混合运算
- 隐式转换
- 强制类型转换
数据输入和输出函数
- scanf读取的是标准输入;printf输出到黑窗口(控制台),输出到了标准输出
- scanf常用的输入类型:%d,%f,%c,%s,可以混合使用
- …是可变参数,参数的数目要与%字母的数量一致
scanf缓冲区原理:初试不考,机试必须掌握
#define _CRT_SECURE_NO_WARNINGS
放在最前面,可以正常使用scanf,只有微软这么要求- 进程运行到scanf函数时,会阻塞,因为需要从标准输入缓冲区读取数据
- 标准输入使用缓冲,也就是说只有按回车,才会触发IO,把这一行的输入内容存入缓冲区
- 标准输入的时候,不管输入什么都是字符,通过%字母的方式转成对应类型,回车本身也是输入了\n
- 按下回车,将输入存入内存,然后触发scanf,从缓冲区读取字符,但是留下了\n
- 只有缓冲区为空的时候,scanf才会阻塞,如果缓冲有数据或者\n,缓冲区都会读取并直接运行
- scanf在读取整型,浮点,字符串时,会忽略回车符,空格符等字符;即scanf会首先删除这些字符,再阻塞
5.scanf循环读取,多种数据类型混合读取,printf讲解
内存地址原理解析
- cpu与内存通过地址总线(类比32/64根线)上高低电平对应的编址找到对应的内存位置,在通过数据总线(32/64跟)将数据传给内存;因此内存里并没有保存所谓的地址,只保存数据总线传来的数据
- 32位与64位原理一样,我们按照32位考虑,因为考试考32位
scanf循环读取整型数%d
- scanf编程出错时返回EOF,EOF是常量,值为-1,所以EOF可以作为判断,用来结束循环
- scanf什么时候会出错:一开始什么都不输入(行首)输入ctrl z,回车,连续三次,
- 如何疯狂打印:读取整型状态下,先输入整数,回车,再输入字符,在回车,开始疯狂打印
- 疯狂打印原因:输入字符不匹配整型接收,不匹配则返回的是0,不是EOF,所以while不能停止,缓冲区保存的还是上一次的数据,
- 如何解决疯狂打印:加rewind(stdin),可以在每次打印之后清空缓冲区,清空之后,再次运行到scanf就会阻塞
scanf循环读取字符%c
scanf混合输入时
- 每次在%c前加上空格,防止输入时的空格被字符变量读取
printf输出
- 输出类型与变量一一对应
- 如何控制输出格式:如%5.2f
6.运算符与表达式
算数运算符
- 先算乘除,后算加减;相同优先级从左到右执行;
- 查看运算符优先级表;
- 算术运算符组成的表达式为算数表达式
- 输入1234输出4321
关系运算符与关系表达式
- 关系表达式的值为真或加
- C语言没有布尔类型,真就是1,假就是0,认为一切非0的值都是真;
- 关系运算符不能连写,否则就会按优先级执行,结果与预期不符
逻辑运算符
- 逻辑运算符组成的试子为逻辑表示,
赋值运算符
- 赋值运算符左边必须是变量,对应一段内存空间
- 如果报错,做操作数必须围坐支,说明等号左边必须是变量
- 加后赋值,减后复制,,,,其实就是简化写法
逗号运算符
- 逗号表达式一般放在while或for循环里
- 逗号表达式整体的值就是最后一个逗号后面的值
自增自减运算符
- 来源于B语言,为了让程序员们习惯使用,从B语言保留下来的
- 完全可以被替代,部分人喜欢用,代码看着简洁
- i++难点在于先后顺序,先用i当前的值运算,然后在执行i+1
- 如,i=-1,j=i++>-1,得到的j是0,因为-1>-1结果为0,然后再算i=i+1;i–同理
- ++i和–i直接按优先级正常运算即可,i先加1或减1,再参与表达式运算
- 总结规律:后++和后–就是拆成两步算
sizeof运算符
- ()放变量,返回的变量的字节数
7.选择循环,作业讲解
关系表达式和逻辑表达式
- 算数运算符(±*/%)优先级高于关系运算符(><>=<===!=)
- 关系运算符高于部分逻辑运算符(&&||),!是第二优先级
- 求表达式的值,先算优先级高的,相同优先级的从左到右算
- 短路运算
- if()大部分放关系表达式、逻辑表达式。少数人直接放了一个值
- if()内的表达式为真,就会执行{}里的语句,为假,不执行{}里的语句
if与else
- if与else if,可以不断地else if,语句只会有一个得到执行
- else不能单独写,必须和if配对
- 写if和else,一定要加大括号,否则容易混淆
while型语句
- 如果搞不清楚终止条件的话,可以先写,最后几部写在纸上一步一步推算
- 不要在while后面加分号,否则就会出现死循环,()内永远不能出现结束
- 出现黑窗口卡住,只有两种情况,一种是scanf,手输入可以输入;另一种是死循环,无法输入
for循环
- 使用比较灵活,不容易出现死循环
continue
- 提前结束本轮循环,后面的语句不再执行,直接进入下一轮循环
break
- 直接终止整个循环,直接跳到外面,执行循环外后面的语句
8.一维数组,字符数组
定义数组
- 类型 数组名[常量]
- c99不支持[]里使用变量,我们平时练习学习时确保使用常量即可
- 定义数组的时候可以初始化,也可以只定义,不初始化
int a[5] = {1,2,3,4,5}
不能写成int a[5];a[5]={1,2,3,4,5}
,因为定义之后,a[5]表示访问第六个变量,就会出现访问越界- 这里先不要考虑动态大小,等到指针再解决
一维数组的内存
- 从0开始,不是从1开始,前人就是这么设定的
- 比如整形数组,每个元素4字节,5个元素就是20字节
- 定整型数组之后,有赋值则按照赋值,没有赋值的空间默认给0
访问越界
- 访问了不属于自己的空间
- 注意在微软的编译器中,先定义的变量在高地址区,后定义的变量在低地址区
- 微软编译器设计,不同的变量之间有8字节的保护空间,mac与linux使用的gcc没有,因为标准C没有规定
- 所以在微软的编译器中,访问越界,很有可能访问到保护空间上了
- 访问越界的各种可能报错,见错误总结
- 调试模式下才会有提醒(内存释放时有检测),实际情况下不会有提醒
- 访问越界危险:非常容易篡改其他变量,影响别人代码的运行
- C语言才会有访问越界的风险,Java和Python没有,C也不想改,因为内存交给高手程序员控制,才会利用更高效;Java或Python将内存管理交给了内存管理器
一维数组的传递
- 数组在传递的时候,只能传递数组的起始地址,并不能传递数组的元素个数,可以再加一个变量用于传递数组元素个数
- 数组进入到子函数以后,子函数能否修改数组?可以
字符数组
- 定义方法与一维数组一致
- 用来存字符串,一个一个的字符组合在一起就是字符串
- 传统的初始化赋值方式
char c[10]={'h','e','l','l','o'}
,这样会很累 - 语言发明者考虑到这点,允许我们这样初始化
char c[10]="hello"
,效果与上面是一样的 - 字符数组就是用来存字符串的,别无其他用处
- %s用来输出字符串的,对应字符数组名或字符串常量
- 初始化字符串数组的时候,一定要预留多个位置,用来保存结束符
\0
,系统自动加上的,否则打印的时候会一直读下去,可能出现烫烫烫,就是cc cc cc,\0
的值就是0 - scanf在读取字符数组的时候,会在结尾自动添加一个结束符
- printf输出字符数组的时候,数组名不用加&,因为数组名本身就是指针,包含了地址,当然写了也没有问题
9.gets与puts,str系列,指针本质
指针阶段会演示整型、浮点型、字符型传递,传递是值把一个变量传递给对应的子函数
函数调用,传递字符数组
- 函数调用(值传递),形参,实参
- 自定义一个函数,并调用函数循环打印输出的时候,只需传递字符数组名,无需传递长度,因为字符数组结尾有
\0
,作为结束条件 - 传递字符数组以后,也可以在函数里面修改字符数组
遇到一个问题
- scanf通过%s读取字符串的时候,遇到空格会匹配结束,因此无法将一行带有空格的字符串存入到字符数组中
gets
- gets只能输出字符串,而printf可以输出多种类数据
- 定义一个字符数组,gets©读取一行,puts©输出一行,可以包含空格
- gets()需要的参数其实是字符数组指针,而字符数组名其实就是指针,保存着字符数组的起始地址
指针的本质
- 指针的本质,用来存储地址,间接访问
- 直接访问:直接访问变量名,或者加上&变量名,读取变量的值
- 间接访问:定义指针变量,初始化的用
&变量名
,这样指针变量就得到了变量的地址值 - 整型指针变量存储整型变量的地址,字符指针变量存储字符变量的地址值,。。。
- 取地址&,也叫引用;*就是从地址变量里读取保存的值,也叫解引用
- 比如,使用printf的时候,可以使用变量名;也可以用*指针名,就是间接访问读取地址上保存的数据
注意
- 定义指针变量的时候,*表示指针变量,*不是变量名
- 不同类型的指针变量不要互相复制
- &和*优先级相同,对同一个变量
- &* p就是p,* &p也是p,这么做没啥意义
- 定义三个指针变量
int *i,*j,*k;
而不是int *i,j,k;
10.上周作业讲解,指针的传递与偏移
指针的使用场景:指针的传递、指针的偏移
函数调用原理
- 调用函数的时候,只是把参数值传递复制了过去,所以在调用函数里改变变量的值,不会影响原来的变量
- 主函数里操作的变量的空间与调用函数里操作的变量空间不是同一个
- C语言函数调用是值传递
指针的传递
- 只有使用指针,子函数才可以改变主函数里的变量
- 调用子函数的时候传递变量的地址,这也是值传递,因此子函数里可以通过地址,间接访问到主函数里变量的空间
指针的偏移
- 数组是特殊的
11.指针与一维数组,动态申请空间(重点)
1.代码分析
int a[3] = { 2,7,8 };
int* p;
int j;
p = a;
j = *p++;
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);
p = a;
让指针变量p,指向了数组a的开头j = *p++;
的过程- 首先把后++去掉,得
j=*p;
,所以此时j=*p为a[0]的值; - 再看
++
之外的另一个运算符(*
)的优先级是否高于++
,如果是高于,要先算另外一个运算符,再整体算++,显然*
没有高于++
而是同级,所以我们可以直接计算p++
p++
,指针类型++表示按照基类型偏移一次,这里就是四字节,也就是指向了数组第二个元素,所以,最后*p=7
- 结果是 2 2 7
2.如果j = *p++;
改成j = (*p)++;
- 由于
*p
整体加上了括号,所以++
操作的是*p
整体,也就是p指向的内存空间里的具体值 - 结果为 3 2 3
3.如果在1的基础上改进,增加代码,第二次输出的结果如何?
int a[3] = { 2,7,8 };
int* p;
int j;
p = a;
j = *p++;
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);
j = p[0]++;
printf("a[0]=%d,j=%d,*p=%d\n", a[0], j, *p);
- 第一轮输出完,结果为 2 2 7,此时的p指向a[1],所以p[0]等于7
j = p[0]++;
计算时,先去掉++,所以j等于7- 再计算++,由于[]优先级高于++,所以是p[0]整体++,所以p[0],*p,a[2]都是8
- a[0]始终不变,结果为 2 7 8
后++小结
- 第一步就是去掉++再计算
- 难点在第二步,如何把++与其他符号结合,需要判断优先级
- 指针能够改动目标内存空间的值,这一点也需要注意
- 编程的最高境界就是时刻清楚自己在操作哪块内存空间
- 这里的重要程度不是很高,用于提升理解能力,看懂大神操作
指针与一维数组
函数调用过程
- 函数调用的本质是值传递,实参赋值给形参
- 数组名里保存的是地址值,也就是指针值,所以不能传递数组的长度
- 函数调用也不能传递类型,完全取决于子函数中形参前面是什么类型,就按照什么类型接收
- 所以业界也称,数组传递时会弱化为指针
- 所以在子函数内,可以通过指针的方式,操作外面的主函数的数据
void change(char d[]){...}
- 写成d[]是没有意义的,因为传进来的仍然是字符数组的指针,还不如直接写成
char *p
指针与动态内存申请
- 数组是一开始就确定下来的,数组是放在栈空间的,栈空间在编译时就确定了
- 动态空间大小不确定,使用的堆空间
malloc
申请空间单位是字节,参数是整型,malloc
申请的空间也会出现访问越界的情况malloc
返回void*
,表示无类型指针;其他函数返回的void
表示什么都不返回malloc
返回的其实就是申请的空间的地址,并且是一个没有类型的指针void*
,这样的好处是,根据实际需要强转为指定的类型malloc
申请的空间依然是通过偏移的,只不过是存在了堆空间里;过完空间以后,需要释放空间free(p)
- 释放完空间之后,这个指针所指向的内存区域就被清空了,可能以后会被系统拿来做其他用处,所以记得把这个指针变量置为null,否则就成为了野指针,这在C语言中很忌讳,容易篡改他人内存空间
- 如果我们需要使用
malloc
返回的指针进行运算,比如偏移操作,我们可以先赋值给其他变量,再去使用赋值后的其他变量
代码例子,演示栈空间与堆空间的差异
char* p[] = "hello";
使用的栈空间
现象1:子函数里可以打印输出字符串,但是返回的指针变量回到外面函数,就不能打印,原因
- 函数调用时,使用的栈空间,栈空间会对着子函数的运行结束而释放,
- 所以,即使子函数返回的地址出来了,此时的地址里的内容也已经被释放了
- 所以调用子函数时,不要返回子函数内的指针,因为用不了
- main函数或者外层函数(没执行完的)的指针不受影响,可以用
char* p = malloc(20);
使用堆空间(记得,malloc返回值强转,否则扣分)
现象:在子函数里定义的p,既可以在子函数里打印,可以返回出去,在子函数外面打印
- 子函数里申请堆空间后,堆空间不会随着子函数的结束而释放,必须free才会释放
12.指针、函数、递归调用、全局变量
day11作业讲解
字符指针与字符数组的初始化(不考,了解)
- 字符串常量区,只能读取,不能被修改
二级指针
- 保存一级指针地址的地址
- 二级指针的初始化,一定是某个一级指针的取地址
创建多个.c文件,自定义头文件
- 源文件目录下新建func.c文件
- 头文件目录下新建func.h文件,注意选择头文件,不是C++文件
- 一个项目可以有多个.c文件,可以让不同的人开发自己的文件,提高效率
- func.h中可以引入stdio.h,main.c中再引入func.c,
.c
文件中添加自定义的头文件用#include "func.h"
,意思就是在当前.c文件路径下搜索对应的文件,项目中看起来是不同的目录,实际就是一个文件目录下。- include其实就是把引用文件的内容拷贝到了当前文件中,其实也就是相当于当前的.c文件引入了
stdio.h
自定义头文件作用
- 自定头文件引入各个所需的标准头文件,我们的.c文件只需要引入一个自定头文件就好,相当于引入了自定义头文件里面的各个标准头文件,简化代码
- 用来声明函数:
main.c
中若想使用其他.c
文件中的自定义函数,需要在自定义.h
文件中声明这个函数 - 其实就算不声明,编译时候也会自动扫描,在当前项目中的.c文件中找到这个函数,但是会有编译警告
为什么创建多个.c文件
- 团队协同时提高开发效率
- 不同的.c文件是分别编译的,这么做提高编译效率
- 所有的函数都是平等的
函数的声明、定义、调用
- 见pdf笔记
- 尽量不要使用全局变量
递归调用 - 函数自己调用自己就是递归
- 递归的表达式类似于数学中的数列的递推公式
- 设置好结束条件,否则会死循环,
- 不是重点,考试的他概率比较低
比如,求n!
int f(int n)
{if(n == 1){return 1;}return n * f(n-1);
}
局部变量与全局变量
- 见pdf笔记
- 局部与全局重名,就近原则
C语言学习笔记(408版本-初级阶段)相关推荐
- go get 拉取指定版本_go语言学习笔记-基础知识-3
相关文档 go语言学习笔记-目录 1.简介 1.1 什么是GO Go 是一个开源的编程语言,它能让构造简单.可靠且高效的软件变得容易.Go是从2007年末由Robert Griesemer, Rob ...
- 安装成功配置环境变量_go语言学习笔记-Windows10开发环境安装和环境变量配置
相关文档 go语言学习笔记-目录 1.安装 1.1.访问 https://golang.google.cn/dl/ 或 https://golang.org/dl/ 下载官方安装包 1.2.选择Win ...
- c语言如何宏定义枚举型结构体,C语言学习笔记--枚举结构体
枚举 枚举是一种用户定义的数据类型,它用关键字enum以如下语法格式来声明: enum 枚举类型名字 {名字0,名字1,...,名字n}: 枚举类型名字通常并不真的使用,要用的是大括号里面的名字,因为 ...
- r语言c函数怎么用,R语言学习笔记——C#中如何使用R语言setwd()函数
在R语言编译器中,设置当前工作文件夹可以用setwd()函数. > setwd("e://桌面//") > setwd("e:\桌面\") > ...
- 【Go语言 · 学习笔记】
文章目录 Go语言 · 学习笔记 一.Go包管理 1. 什么是Go语言中的包 2. 包的命名 3. main包 4. 导入包 5. 远程包导入 6. 命名导入 7. 包的init函数 二.Go开发工具 ...
- codesys工程ST语言学习笔记(五)打开压缩文件projectarchive失败,指定的工程不能被加载
codesys解压文件projectarchive失败 不会编译程序或者建立工程的点击第一篇文章codesys工程ST语言学习笔记(一)建立工程与编译 不会编译程序或者建立工程的点击第一篇文章code ...
- R语言学习笔记——入门篇:第一章-R语言介绍
R语言 R语言学习笔记--入门篇:第一章-R语言介绍 文章目录 R语言 一.R语言简介 1.1.R语言的应用方向 1.2.R语言的特点 二.R软件的安装 2.1.Windows/Mac 2.2.Lin ...
- C语言学习笔记09-数组、字符数组、字符串数组、二维数组(单字符输入输出putchar、getchar,字符串输入输出的scanf、gets、puts)
C语言数组 数组作用:可以用来保存很多记录(可以看成一种大容器).一些简单游戏也基本由数组实现,如游戏地图(二维数组)等等. 一个数组 划分 多个单元(下标区分) -存放-> 多个同类元 ...
- 易语言学习笔记——基础篇
易语言学习笔记20180710 一. 易语言的数据类型可以分为基本数据类型和特殊数据类型 1. 其中基本数据类型分为: ① 数值型 ② 逻辑型 ③ 日期时间型 ④ 文本型 ⑤ ...
- 易语言学习笔记——命令篇
易语言学习笔记20180711 一. 命令概述 1. 什么是命令:命令是一个功能调用的开始. 2. 命令的参数:调用一个功能方法时候输入的数据或者条件. 3. 命令的返回值:调 ...
最新文章
- python知识:稀疏矩阵转换成密度矩阵
- 洛谷P2995奇数偶数
- 如何在Web应用里消费SAP Leonardo的机器学习API
- musictools怎么用不了_夏天少不了一只草编包,怎么搭配才不像“买菜用”?
- 北京一女子乘公交车遇车祸 惨遭钢筋穿胸
- Solr4.3整合到Tomcat中并添加MMSeg4j中文分词器
- 用SecureCRT在windows和CentOS间上传下载文件
- 高效记忆/形象记忆(04)数字编码记忆
- Ajax前台传数组,Java后台接收方式
- js判断设备是PC端还是移动端
- 传统模式下安装linux,在将引导顺序更改为传统模式或在传统模式下安装操作系统时找不到引导设备...
- Unity 如何获取安卓设备的SN号
- html 编辑器 拖动,可视化拖拽页面编辑器 一
- 【连通域检测】基于形态学处理的连通区域检测matlab仿真
- 计算机语言中str是什么意思,python中str函数的作用是什么
- 2019-2022年中国定期存款基准利率走势(附三个月定期、半年定期、一年定期、二年定期及三年定期存款基准利率)[图]
- Java遍历目录下的所有文件
- GPGPU-sim环境搭建教程(详细)
- 微信小程序开发之——个人中心-订单物流查询(8)
- php access编程实例,PHP连接操作access数据库实例,access实例_PHP教程