GMTC全球大前端技术大会(北京站)门票9折特惠截至本周五,点击立减¥480 了解详情
写点什么

5 分钟 get C 语言核心要点

2020 年 6 月 24 日

5分钟get C语言核心要点

引言

笔者有十余年的 C++开发经验,相比而言,我的 C 经验只有一两年,C 比较简单,简单到《The C Programming Language》(C 程序设计语言)只有区区的 200 多页,相比上千页的 C++大部头,不得不说真的很人性化了。


C 精简的语法集和标准库,让我们可以把精力集中到设计等真正重要的事情上来,而不是迷失在语法的海洋里,这对于初学者尤其重要。虽然 C 有抽象不足的缺点,但我更喜欢它的精巧,只需要花少量的时间,研究清楚它每一个知识点,看任何 C 源码就不会存在语法上的障碍,大家需要建立的知识共识足够少,少即是多,少好于多。


我教过 6 个人编程,教过文科生,教过小学生,教过女生,教过 HTML,教过 JAVA,也教过 C++。最近,我在教我小孩编程,他只有十岁,很多人建议我选择 Python,但我最终选择了 C,因为 C 简单且强大,现在看来,好像是个不错的选择。


类型

C 是强类型语言,有 short、long、int、char、float、double 等 build-in 数据类型,类型是贯穿 c 语言整个课程的核心概念。


struct、union、enum 属于 c 的构造类型,用于自定义类型,扩充类型系统。


变量

变量用来保存数据,数据是操作的对象,变量的变字意味着它可以在运行时被修改。


变量由类型名+变量名决定,定义变量需要为变量分配内存,可以在定义变量的同时做初始化。


int i; float f1 = 0.5, f2= 0.8;
复制代码


常量

const int i = 100; const char* p = "hello world";
复制代码


运行中恒定、不可变,编译期便可确定。


数组

光有简单变量显然不够,我们需要数组,它模型现实中相同类型的多个元素,这些对象是紧密相邻的,通过数组名+位置索引便能访问每个元素。


二维、三维、高纬数组本质上还是线性的,二维数组通过模拟行列给人平面的感觉,实际存储上还是连续内存的方式。


数组是静态的,在定义的时候,数组的长度就已经确认,运行中无法伸缩,所以有时候我们不得不应付扩充多分配一些空间。数组元素不管用多用少,它都在哪里,有时候,我们会用一个 int n 去记录数组实际被使用的元素个数。


函数

函数封装行为,是模块化的最小单元,函数使得逻辑复用变得可能。


C 是过程式的,现实世界都可以封装为一个个过程(函数),通过过程串联和编排模拟世界。


用 C 编程,行为和数据是分离的。调用函数的时候,调用者通过参数向函数传递信息,函数通过返回值向调用者反馈结果。


函数最好是无副作用的,函数内应该尽量避免修改全局变量或者静态局部变量,更好的方式是通过参数传递进来,这样的函数只是逻辑的盒子,它满足线程安全的要求。


有了变量和函数,就可以编写简单的程序了。


控制语句

分支:if 、else、else if、switch case、?:


循环:while、do while、for


break、continue、goto、default


结构体

build-in 数据类型不足以描绘现实世界,或者用 build-in 类型描述不够直接,结构体用来模拟复合类型,它赋予了我们扩充类型系统的能力,我们把类型组合到一起构建更复杂的类型,而每个被组合的成分就叫成员变量。


结构体内的成分,对象通过点(.)运算符,指针通过箭头(->)访问成员。


指针

C 的灵魂是指针,指针带来弹性,指针的本质是地址。


需要区分指针和指针指向的对象,多个指针变量可指向同一个对象,一个指针不能同时指向多个对象。


指针相关的基本操作包括:赋值(修改指针指向),解引用(访问指针指向的对象),取地址(&variable),指针支持加减运算。


因为指针变量要能覆盖整个内存空间,所以指针变量的长度等于字长,32 位系统下 32 位 4 字节,64 位系统下 64 位 8 字节。


指针的含义远比上述丰富,指针跟数组结合便有了指针数组(int* p[n])和数组指针(int (*p)[n]),指针跟函数结合便有了函数指针(ret_type (*pf)(param list)),指针跟 const 结合便有了 const char*/char* const/const char* const,还有指向指针的指针(int **p)。


既可以定义指向 build-in 数据类型的指针,也可以定义指向 struct 的指针,void*表示通用(万能)指针,它不能被解引用,也不能做指针算术运算。


函数指针与回调(callback)

c source code 被编译链接后,函数被转换到可执行程序文件的 text 节,进程启动的时候,会把 text 节的内容装载到进程的代码段,代码段是 c 进程内存空间的一部分,所以任何 c 函数都会占一块内存空间,函数指针就是指向函数在代码段的第一行汇编指令,函数调用就会跳转到函数的第一个指令处执行。


函数指针经常被用来作为回调(callback),c 语言也会用包含函数指针成员的结构体模拟 OOP,本质上是把 C++编译器做的事情,转给程序员来做(C++为包含虚函数的类构建虚函数表,为包含虚函数的类对象附加虚函数表的指针)。


字符串

char*是一类特殊的指针,它被称为 c 风格字符串,因为它总是以‘\0’作为结尾的标识,所以要标识一个字符串,有一个 char*指针就够了,字符串的长度被 0 隐式指出,跟字符串相关的 STD C API 大多以 str 打头,比如 strlen/strcpy/strcat/strcmp/strtok。


内存和内存管理

指针提供了 c 语言直接操作底层内存的能力,c 程序区分栈内存和堆内存,栈内存是函数内的局部变量,它随程序执行而动态伸缩,所以不要返回临时变量的指针,栈内存容量有限(8/16M),所以我们要避免在函数内创建过大的局部变量,要警惕递归爆栈。


堆内存也叫动态内存,它由一个叫动态内存配置器的标准库组件管理,glibc 的默认动态内存配置器叫 ptmalloc,初始版本有性能问题,但后面用线程私有解决了竞争改善了性能。动态内存配置器是介于 kernel 与应用层的一个层次,从内核视角看 ptmalloc 是应用程序,从应用层来看 ptmalloc 又是系统库。malloc 跟 free 必须配对,这是程序员的职责,动态分配的内存丢失引用就会导致内存泄漏,指向已释放的内存块俗称野(悬垂)指针。


预处理

从 c source file 到可执行程序需要经过预处理-编译-汇编-链接多个阶段,预处理阶段做替换、消除和扩充,预处理语句以 #打头。


宏定义,#define,宏定义可以用\做行连接,#用来产生字符串,##用来拼接,宏定义的时候要注意加()避免操作符优先级干扰,可以用 do while(0)来把定义作为单独语句,#undef 是 define 的反操作。


#if #ifdef #ifndef #else #elif #endif 用来条件编译,为了避免头文件重复包含,经常用 #ifndef #define #endif。


#include 用来做头文件包含;#pragma 用来做行为控制;#error 用来在编译的时候输出错误信息。


FILELINEDATETIME、_STDC_等标准预定义宏可以被用来做一些 debug 用途。


#typedef 用来定义类型别名。比如 typedef int money_t;money_t 比 int 更有含义。


typedef 也能用来为结构体取别名,有时候会这样写:


typedef struct{  int a;  int b;} xyz_t;
复制代码


这样在定义结构体变量的时候就可以少敲几下键盘。


typedef 也可以用来重定义函数指针类型,比如 typedef void (*PF) (int a, int b); PF 是函数指针类型,而非函数指针变量。


枚举

枚举能增加代码可读性和可维护性,枚举本质上是 int,只是为了更有含义,将有限取值的几个 int 值放在一组,比如定义性别:enum sex { male = 1, female };


可以在定义的时候赋值,比如 male=1,后面的值依次递增 1,如果不赋值则从 0 开始。


联合体(union)

结构体和联合体(共用体)的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。


union u_data{    int n;    char ch;    double f;};
复制代码


其实本质上,联合体就是对一块内存的多种解释,大小按最大的来。


位域(bitfield)

struct SNField{       unsigned char seq:7 ;        // frame sequnce       unsigned char startbit:1 ;   // indicate if it's starting frame 1 for yes.};
复制代码


节省空间,在面向底层的编码,或者编写处理网络等程序时候用的比较多,注意这个语法特征是跟机器架构相关的。


位操作

  1. 位与 &

  2. 位或 |

  3. 位取反 ~

  4. 位异或 ^

  5. 位移 << >>


static、extern、register、volatile、sizeof、inline

  1. static 修饰全局函数,表示模块内(编译单元)内可用,不需要导出全局符号。

  2. static 修饰局部变量,意味超越函数调用的生命周期,不存储在栈上,只会被初始化 1 次。

  3. extern 声明外部变量。

  4. register,寄存器变量,建议编译器将变量放在寄存器里。

  5. volatile,告诉编译器不要做优化,每次从内存读取,不做寄存器优化。

  6. sizeof 求大小,可以作用于变量,类型,表达式。

  7. inline 内联,就地展开,避免函数栈帧建立撤销和控制跳转的开销。


可变参数

void simple_printf(const char* fmt, ...)va_list、va_start、va_arg、va_end

复制代码


C 的高级感

泛型:linux 内核链表,通过 offset 和内嵌 node,写出泛型链表,参考:https://www.cnblogs.com/wangzahngjun/p/5556448.html


OOP:通过定义带函数指针成员变量的结构体,在运行中,为结构体对象设置上函数指针,模型运行时绑定,实现类似 OOP 多态的感觉。


GNU C 扩展

GNU C 扩展不是标准 C,建议以符合标准 C 的方式编写 C 代码,但如果你阅读 linux kernel code,你会发现有很多有趣看不懂的语法,它来自 GNU C 扩展,它确实也带来了一些便利性。


比如结构体成员可以不按定义顺序初始化:


struct test_t { int a; int b; }; struct test_t t1 = { .b = 1, .a = 2 };
复制代码


比如可以通过指定索引初始化数组:


int a[5] = {[2] 5,[4] 9}; 或 int a[5] = { [2] = 4, [4] = 9 };相当于int a[5] = {0, 0, 2, 0, 5};或者int a[100] = {[0 ... 9] = 1, [10 ... 98] = 2, 3};
复制代码


比如 0 长度数组


struct foo{   int i;   char a[0];};
复制代码


比如用变量作为数组长度


void f(int n){    char a[n];    ...}
复制代码


比如 case 范围,case ‘A’ … ‘Z’   case 1 … 10


比如表达式扩展({…}),比如三元运算符扩展…


更多扩展请参考:https://my.oschina.net/LinuxDaxingxing/blog/751319


作者:华为云专家 人民副首席码仔


2020 年 6 月 24 日 14:311287

评论

发布
暂无评论
发现更多内容

架构师训练营 - 第 7 周课后作业(1 期)

Pudding

《Python:Python编程简介:计算机编程和机器学习入门指南》

计算机与AI

Python

谈谈敏捷开发概念和迭代开发方案

Learun

敏捷开发

【涂鸦物联网足迹】涂鸦云平台接口说明

IoT云工坊

人工智能 物联网 API sdk 云平台

又一道比较运算符相关的面试题让我明白基础很重要

Gopher指北

golang

魏际刚:精准谋划我国供应链发展新方位

CECBC区块链专委会

供应链 物流

从智慧计算的点、线、面,读懂浪潮AI的进化轨迹

脑极体

揭秘在召唤师峡谷中移动路径选择逻辑?

华为云开发者社区

算法 地图 最短路径

【运维思考】如何做好云上运维服务?

嘉为蓝鲸

云计算 运维 数字化转型 数据中心 云服务

金融科技的未来

CECBC区块链专委会

金融

初级工程师职场生存要点

javaadu

程序员 职场成长 开发日志

帮助企业摆脱困境,名企归乡工程师:能成功全靠有它!

Philips

敏捷开发

百亿级数据分表后怎么分页查询?

艾小仙

Java MySQL 数据库 编程语言 分库分表

架构师训练营第 1 期第 7 周总结

du tiezheng

极客大学架构师训练营

真香,数位Ali高级工程师遍览中外名书,终成顶级网络编程笔记

周老师

Java 编程 程序员 架构 面试

价值超10亿美元的直播系统架构图是什么样子的?

冰河

系统架构 高并发 高性能 亿级流量 直播架构

一款区块链钱包开发需要多少钱?数字资产钱包开发搭建

13530558032

LeetCode题解:77. 组合,递归回溯,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

架构训练营 - 第7周课后作业 - 学习总结

Pudding

终于啃完了Java核心原理+框架“面试圣经”成功五面上岸美团

小Q

Java 学习 编程 架构 面试

浅谈API网关(API Gateway)如何承载API经济生态链

华为云开发者社区

API 网关

做个别人家的网页

MySQL从删库到跑路

html/css 网页设计

CDN是什么?

德胜网络-阳

爆料!前华为微服务专家纯手打500页落地架构实战笔记,已开源

996小迁

架构 面试 分布式 微服务 程序人生

害怕重构?都怪我太晚和你介绍该如何重构,现在我来了

小Q

Java 学习 程序员 面试 重构

Apache DolphinScheduler 是如何走进Apache的

海豚调度

大数据任务调度 数据湖调度 DolphinScheduler Apache DolphinScheduler

如何稳扎稳打推进数字货币进程

CECBC区块链专委会

数字货币

数字货币OTC交易所开发,交易所搭建方案

13530558032

USDT承兑支付平台技术开发,承兑商币支付交易平台搭建

13530558032

从一场“众盟科技云滇之播”,我们发现了美食直播的商业与公益价值

脑极体

架构师训练营第一期 - week8

习习

5分钟get C语言核心要点-InfoQ