C语言基础知识入门大全、C语言编程大白话教程笔记

C/C++/C#/C++Builder

之前说准备写个自己入门C语言的教程,顺便记录心得,总结,结果就列了一个提纲,没有时间来凑文字,先放这。有时间慢慢来写写。

C我学的很早,也用了很多年(08开始的),但是仅仅是自己使用到的部分用过而已,博大精深的c,c++还会继续学习。

一。序言,铺垫,准备

  • 为何有这个笔记教程(P1.1)
  • 有效的学习方法:为用而学,带着疑问而学,寻找答案而学,最有效(P1.2)
  • C产生的年代(P1.3)
  • C主要用于写什么样的程序
  • 实际工作中,我们用C做什么
  • 学习C用什么工具IDE,实际工程中使用什么工具

二。计算机基本组成与C的关系

  • 基本模块
  • 运行原理
  • C编译到运行的流程
  • 初级知识和烧脑的知识
  • 内存的样子

三。C代码最基本的文件形式

  1. 简单程序,写在一个文件里面,扩展名为.c。则变量,函数声明放在最上部main函数之前。
  2. 声明写在.h头文件中,实现的代码写在.c文件中。方便第三方开发时引用头文件

Main函数,程序入口

  1. 有且只有一个
  2. 接收命令行参数
  3. 结束时,程序就退出了。
  4. 基于状态的检测并持续执行内部有个while

代码书写要求,基本规范

注释:介绍,解释程序代码的作用,作者,日期等

  1. 过去,强调函数说明,参数说明。写上作者,日期,现在针对改动的细节,应该写上详细的原因,日期。如何实现的。这点显得很重要了。

标识符:为变量,函数等命名的问题

基本数据类型:规定:它里面放的值是什么意义,值的范围,怎么解释它,它长度是多少字节

  • 类型和范围
  • 不同操作性系统对int的定义不同,导致了变量长度依赖,如何解耦这个依赖呢? _int2 _int4 _int8或者用define定义自己的类型
  • 涉及变量长度敏感,比如文件格式,单片机存储,特殊协议时,要特别留意变量长度在不同编译器版本的适配

基本的控制台输出printf: 不仅输出显示给操作者看,也从CGI中输出到web服务器实现c写网页

常量与字面量,左值和右值

  • 常用是一个字面量或者说直接就是值的代称,它看起来像变量那样,其实是为了方便使用引用它,编译后它已经被后面的字面量直接替代进到程序内部
  • 常量就是固定的了,不改变它。用常量是为了不产生那么多无法解释的”魔数“,不会产生莫名其妙的紊乱。比如id为5还是单价为5.
  • 字面量是实际的值,比如5,“abc”,’A’
  • 常量,常数,字面量,他们都是一经定义不再改变。

四。变量:数据临时存储位置

  • 主要变量类型就是两种
  1. 简单数字:以多个字节很单纯地来存储能表示不同范围大小的数字
  2. 复杂数字:存储数字和指数,比如浮点数。它实际上是一个结构,不同bit是不同意义。
  3. 字串:理解为hello为5个字母的排列
  4. 声明变量,是否赋初始值,运算中赋值
  5. 什么时间给初始值,什么时间不用给初始值
  6. 小心逻辑运算时给变量赋值造成bug
  7. 结构:基本变量组合起来表示一个部件的多种特性
  8. 联合:一块空间有两个名字可以读写但他们相互覆盖
  • 一般编程我们用不着,我们没有了节省1K字节的习惯。
  • 特殊情况用得上,比如要节省这几个字节,或者对于文件格式来说,没必要造成冗余的字节。文件越短越精炼越好。
  1. 静态的变量还是动态的变量。加载时是否存在而定
  2. 字面量被编译到代码一起,变量预留一个固定空间,你可以改变它,改变程序走向,也可以加密它,甚至动态解释运行它。

全局变量与局部变量

  1. 变量的作用域
  2. 声明在什么位置
  3. 其他文件对变量的引用
  4. 存储类别
  • 自动(auto)、
  • 静态(static)、
  • 寄存器的(register)
  • 外部的(extern)。

数据类型转换

  • 自动转换
  • 强制类型转换

运算符

  • 算术运算符
  • 自增自减
  • 变量赋值
  • 比较运算符
  1. 运算的结果是布尔值,true或者false。 c里面是0和非0
  • 逻辑运算符
  1. 什么是与或非,异或
  2. 电子学的逻辑与或非可以化简,但是编程中我们按人能理解的逻辑来写
  3. 逻辑的短路算法OR找到一个就满足
  4. 学过电阻的串并联吗?没学过就想开关的串并联。我们必须把它归纳为是串模型还是并模型,才好编程。
  • 三目运算?:可以进行一个二值判定带入表达式,简化版的if else。
  • 运算符优先顺序
  1. 我们有必要使用小括号明确表示运算先后顺序,不依赖于优先顺序,因为表达式太复杂我们无法一眼望见。
  2. 不要采用非常费解的写法。看似高超,实则费事。

流程控制,程序分支

  1. 世上最简单逻辑判断,是怎么样,否则怎么样 IF ELSE
  2. 想用最简单的IF判断但是一次又判断不完全IF ELSEIF ELSEIF ELSE
  3. 逻辑函数的值跟二进制一样是二值的
  4. 专业逻辑写法
  5. 举个逐渐复杂的逻辑例子
  6. 针对具体值的判断用if写就很烦琐采用switch case
  7. 单个字符也可以用switch case
  8. 多个字符串也想用switch case怎么做呢
  9. 多个值同时执行相同一段代码
  10. 都判断完了,万一有个漏网之鱼怎么办用default包含

循环:要遍历数组就引出了

  • for循环,知道起止次数
  1. for循环王牌循环模式
  • while 不明确次数,根据条件判定结束
  • do while也可以改写成while形式
  1. 只知道要执行什么和什么条件结束,没有明确的次数 用while
  • 跳过后面的代码不执行continue
  • 从while中终止
  1. 从循环中直接转到下一次循环用continue
  2. 提前跳出循环 *循环的嵌套
  3. 如果要访问二维数组要使用两个for套在一起
  4. 多重嵌套中如何跳出:跳出内层,而且要跳出外层。
  • 无限循环
  1. 循环又叫迭代,代表了演进但是又有所变化的意思。

数组

  1. 最简单数组的模型就是排成一列的事物
  2. 二维数组是排成行列的事物
  3. 三位数组直观可以想象成魔方,它有行列还有层
  4. 三维数组另外一个例子:火车可以分车厢,车厢内可以分排,每排可以分座位。
  5. 多维数组的本质是对事物的层次切分
  6. 不管几维数组,内存存储模式都是单列,内存是线性的。
  7. 内存从寻址上说可以分页,分段,分块。但每个byte,bit都可以用数字来唯一表示。它也是线性的。直线的。

函数

  1. 内部函数static修饰符(限定不对外可见)等同于其他语言中的private。它的作用通常是被其他函数所调用构成完整都的程序。可以理解为子函数,不对外公布。
  2. 外部函数 extern修饰符
  3. 声明
  4. 实现
  5. 调用
  6. 函数参数传递方式: 参数传值,参数传地址, 参数传指针(后两种本质是一样的) 如果传值的话,会产生副本,就是会生成新的对象,花费时间和空间,而在退出函数的时候,又会销毁该对象,花费时间和空间。
  7. 返回值
  8. 什么情况下需要把代码独立成一个函数

递归

函数里面又调用自己,执行到调用自己时,当前压栈,进入一个新的函数调用,最后各层出栈继续执行。

内存申请: 我们要用到一整块内存写入数据时的最好办法,比如要制作一个特殊格式的文件

  1. 申请内存获得地址malloc
  2. 根据内存块起始地址加上偏移量来使用内存
  3. 初始化或者抹掉信息
  4. 使用完归还释放否则造成无法再次利用free
  5. 内存申请与数组创建内存块的区别
  6. 内存泄露 1.2 尽量是调用者申请内存,传入指针,不在函数内部申请空间并返回。这样内存容易被检查到。 1.3 局部的动态内存,做到严格配对申请和释放。动态申请的内存不使用时要及时free,最起码在程序最末尾,return之前要释放内存。避免此问题,一是malloc和free配对。在提前return时,也要检查是否fee。如果没有提前return。很容易检查配对。 1.4 数据在多个指针之间传递和保留内存时,要严格规定好谁释放。是否成功释放。需要一个检核。

堆heap、栈stack

栈,就是栈板,仓库里面用栈板放东西,最里面的东西如果要拿出来,得把外面的取出来先。就是先进后出。用于类似递归和程序分支,函数调用等保留现场。

当前要执行的函数或者程序分支,在栈顶帧开辟的区域运行局部变量,以及返回值。

读写文件

  1. 文件种类总的来说是可读的字符型和不可读的二进制型
  2. 如何读取
  3. 如何写入 a. 字符型的写入 b. 格式化数据(二进制)如何写入 c. 用什么工具观察写入到的二进制: ultraedit,十六进制观察。学会按字节看文件。

C效率体现在什么地方:借助地址+偏移量进行魔幻似的直接修改。这是C的精髓

a. memset b. memcpy c. memcmp 2. 对 &|位运算等比较低阶 3. 指针取地址等操作非常直接干脆。减少了很多对变量的时间消耗。 4. 机器硬件,电脑平台,手机平台等的开发首先考虑的是c的移植。这决定了它在编译到最终可执行码的效率上面比其他语言是要快。我只能这样认为。 5. 保持了轻装上阵,这是目前c还能被使用的最关键因素。

位于位操作

  • 电路中对BIT操作的用法
  • 位的操作符
  • 如何从一个字节或者几个字节的类型(比如int)中取出某个bit: 移位,& 运算 | 运算。 a. 对位操作的最恰当最实用例子 网络的网罩network mask b. 对BIT,byte,内存块的简便操作,对低阶的机器,单片机,嵌入式来说,C/C++以上的语言都不直接提供 c. 如果单片机内存足够大,比如100K字节,针对BIT的操作对于高层逻辑来说不重要但是对效率来说,C仍然有它的优势 d. 我们不会再节省1bit,但是对于效率非常讲究的算法时,比如有一万个开关。如果用bit表示一个开关量,则我们需要很少的内存。而且查看它的状态可以用位的操作。非常方便。 e. bit看起来不需要节省,但是对于文件编码,则非常重要。表示开关量的话,它能很少字节。 f。如果某种传输协议。头一个byte表示协议的话。不同bit表示不同参数的话。这是bit的意义也非常重要,它精简了传输的数据量。

魔术大师:指针

  1. 指针的本质就是直接操作变量的位置,地址,或者说相对偏移量,或者说变量的一个“柄”。本质是变量声明在内存中。
  2. 指针快的本质是不用复制一个相同的样本,“隔空取物”,直接而干脆,简单粗暴。
  3. 指针的害处:基本没害处。因为你可以不用。不小心可以写数据到本不应该写的地方。这是最大的害。但指针肯定是给能操作它的人来用的。指针没用对,最多算BUG。不害怕。
  4. 指针本质是啥,如何存储,它表现出来是什么。其实就是一个变量。这是它存的都是是另外一个变量的地址。比如信封上面写了收信者的地址。一个道理。信封大小就是指针内存的大小。指针里面的内容就是寄件地址本身。对寄件地址的操作就可以把信寄往目的地。
  5. 声明和使用指针有两种,第一就是一开始就有了指示位置,比如动态申请的内存块。第二种是运行时去取一个别人的地址。就像你抄下朋友的住址一样。
  6. 魔咒:指针的指针的指针的指针。是什么玩意?其实就是数据结构中的链。指针可以悄无声息地记录下其他变量,对象,结构,字符串的位置,魔幻的操作它。被指向的事务根本无法察觉。可以理解为链,也可以理解为字典和搜索引擎的索引。索引为什么快。本质就是对于指针的预处理。试想:预先知道朋友家的地址和门牌号,还用不断打电话询问吗,不用。这又提示我们,要对复杂信息进行处理,分层建立多级的索引,或者叫指针,就能快速处理数据。
  7. const 指针 const intp=&a // p都不能变化 intconst p=&a; // p不能变化 const intconst p=&a; //p 中存放的内存单元的地址和内存单元中的内容都不可变 因为指针本身可以被轻易改变,为了保护首地址,用改变偏移量来取值。就是:*(Point+Offset)方式来访问。保护基准地址不被擅改。
  8. 使用指针的习惯,指向具体事务,或者=NULL
  9. 指针运算 加减的都是一个具体数据类型的宽度(字节数)。
  10. 要不就是指针变化,要不就是指针+偏移量的变化,来操纵数据。
  11. 使用指针的我觉得不用追求技巧,晦涩难懂的技巧不必使用。自己熟悉的方式,可控,不搞bug。

指针退化

int len, size; char a[100] = {0}; len = strlen(a); //len为0,strlen查找到第一个\0返回,a退化为指针 size = sizeof(a); //size为100,实际的内存占用1001。a不退化。 char a[100] = {1,2,3,4,5}; len = strlen(a); //len为5,strlen查找到第一个\0返回,a退化为指针 size = sizeof(a); //size为100,实际的内存占用1001。a不退化。 数组退化为指针问题 1、数组作为函数入参后,会退化为指针。 2、char a[100]数组被strlen(a)后,退化为指针。 3、在带数组型入参函数内对数组运算sizeof就不对了,因为被退化为了指针,因此传递的时候要加一个参数lenth。

  • 不能依赖这样的不确定性。正常应该传入地址和大小size。
  • 什么叫退化,其实就是变成了不知道长度的一个地址,丢失了长度。需要单独传长度参数。

void和void指针

  • void 作为返回参数时,代表无参数,无返回
  • void p时,本质代表这个指针可以指向任何数据类型。不具体致命数据类型。方便写通用函数。 void memcpy(void* dest, const void* src, size_t len); //当然这个函数内部是不管你什么参数地址给他,它是把数据当做一段byte数组来循环执行拷贝的。也就是说void到具体操作时,仍然要具体到数据类型。
  • void *p根据需要用强制类型转换,转换到其他类型。
  • void *p方便我们写一个指向函数的参数,实现泛型编程

二级指针: 指向指针的指针

  • int **p;

二维数组指针

int a[4][5]; int (*p)[5]=a; int (*p)[4][5]=a;

指针数组

数组里面放的都是指针 int *p[3][3]

函数指针

int (*pf)(int, int) = &Add; //实质,指向函数代码段的起始位置

另一个魔术大师:取地址 & 计算符

  1. 本质,和指针是一样的。作用也是一样的。但他们是两个不同的事物。
  2. 指针是一个装地址的容器,比如信封壳。 而地址是地址本身,是地址本身。
  3. 对使用指针和取地址的焦虑:毫无必要。在充分理解的基础上使用。并且依赖IDEden智能提示,轻松加愉快。

如何驾驭指针和取地址

  1. 用久了就不怵了。配合IDE调试。只有正确无误的程序才能得到正确无误的结果。有bug的程序可能偶尔出一个正确结果。但不可能永久正确。
  2. 现在的IDE已经不是DOS时代了。写程序智能化多了。通常我们看到:期望什么值,你送入的是什么值。你就明白了。

如何调试程序

  • 现在IDE普遍支持debug模式编译,可以下断点,逐行执行,可以跟踪到变量值,函数内部。
  • 弹出提示,运用到本地无法调试,比如要取数据库的值,编译环境无法联网时。
  • 写日志,有时逻辑判断非常复杂,而且不方便调试,比如它要求处理速度,而且连续运行,或者过程很随机,复杂。可以写日志。把需要的异常状态记录下来,进一步分析出现这个异常是什么导致的。
  • 写文件的情况下,用ultraedit打开文件分析指定位置的十六进制。对比分析

需求的拆分

  1. 需求提出的可行性,用什么技术来实现
  2. 拆分我要用什么方式,分哪几个模块,如何组合调用他们才能完成伟大的任务
  3. 定义模块模组,定义变量,结构,或者数据库表
  4. 一个小任务又要拆分成几个大特质函数来实现。比如购票,支付,退票,打单。这是在业务逻辑定义上可以容易分割的。
  5. 一个函数,如果代码太长,比如超过千行了。就证明这其中某些小的部分是可以独立成为一段代码的。我们再用下一级函数来实现它。把它归纳出来,写成一个完成部分功能的函数。比如购票可以再次拆分:首先检索票源,核对数量,选择票,比较价格,计算价格,出票。
  6. 可读的程序比效果高更重要。有时为了好阅读,不得不做出取舍折中。记住我们用c。目标就是高效,但是高效的同时,要好读,容易理解,后续容易维护改进。
  7. 规范。比如define定义常数,变量的命名,模组的命名,注释的书写,详细解释。我们都得严格遵守,否则写出的代码不仅漏洞百出,无人敢接手继续维护,而且效率低下。

如何提高自己的实际编程水平

  1. 为什么看入门的教程老是入不了门? 很多次煞有介事地立下宏伟计划说要学习某门语言,结果一拖就是几年。反倒是有几次逼急了。非要写个东西,才把自己给逼到某个语言里来了。实际感受啊。
  2. 有针对性的解决问题,才能对知识有很多索取。一定要练实际的例子,不要去练解决数学问题。解决数学问题不是编程的主要工作。比如写一个文件格式转换,里面可以碰到许多的知识空缺和亟待解决的难题,这个难题是做练习题的数倍,数十倍。这就是用实际案例来学习的好处。
  3. 有人说为何在初学期间就要做很复杂的练习呢。我学习语言包括我用PHP和JAVASCRIPT,我都是拿起来就做任务,当时没有时间其他办法了,遇到问题再找资料。逼着自己马上进入真正的编程。如果你不要永远都在入门徘徊的话,听我的。
  4. 多思多想多总结。项目历练人,这个历练绝非看书,练习,搜索,单纯思考所能累计的。
  5. 为什么解决实际问题能极大极快提高编程水平:实践的问题很多,可能在一行代码上面卡住,就要读一堆的别人的资料。要尝试很多技术手段,很多知识才能解决问题。这个过程真实有效,而且毫不给你留情面。这种情况下,你会穷尽一切办法。