老师不讲的C语言知识
老师不讲的C语言知识
导语:
对于工科生,C语言是一门必修课。标准C(ANSI C)这个看似简单的语言在硬件底层编程、嵌入式开发领域还是稳坐头把交椅。在20年5月份,C语言就凭借其在医疗设备上的广泛应用,时隔五年重回编程语言榜首。
同学们在拿到学分之后还有没有使用这门“手艺”呢?
想做软硬件项目的同学还需要补足哪些知识呢?
不论是正在学习还是曾经学习过C语言的同学,这篇文章总结的一些要点能提供一个新的角度来理解C语言的设计理念和特性。
一起来看看吧!
关键字
一共有多少个关键字?这个的确不好说,在C99和C11里都添加了新的关键字,也有的关键字由于过时淡出了我们的视线。下面这些关键字的用法都掌握了吗?
auto
它可谓默默无闻,不少人应该知道它没什么用——局部变量默认就是auto类型。除此之外,auto变量存放在动态储存区中的栈区。也就是这种变量时有时无,寿命可变,自动(auto)管理。
注意:正因为来去自如,创建的局部变量不会自动初始化为零!!切记
但是,在C++11里auto翻身了,可以用作类型推导。比如:
//以前
double a=10.5;
//现在
auto a=10.5;
//这样就可以把a作为double类型。
可是这和C有什么关系?
static
你能想到static的几个用法?有人说是三个,我觉得就两个。
- 修饰全局变量或函数时,它的作用是仅限本文件访问。C语言里没有命名空间,如果不加static,不同文件里的同名变量会引起混淆,毕竟他们的作用域相同。
- 修饰局部变量,成为存放在静态储存区的静态变量。生命周期为整个程序执行过程。与auto不同,静态变量在程序开始之前就初始化完毕。这也就是常说的第三个用法,将局部变量初始化为零。其实这只是变量存放在静态储存区的一个特征,所以不单独拿出来。
volatile
大多数学校课程是不会用到它的,接触单片机和多线程就能懂得它的重要性。
volatile的意思是”易变的、无常的“,名副其实。
volatile是对变量的修饰,比如volatile int flag=0;它是对编译器的提醒:
”嘿,这变量是变化无常的,你可小心点!“
这针对的是编译器的”小聪明“——优化。
举个例子
int flag=0;
...
flag=1;
if(flag==1)
计算机运算要先把变量从内存加载到寄存器,这一步是耗时间的。编译器一看,前脚我才让flag=1,这个flag还在寄存器里,到下一句判断之间也没有能改变flag的语句,那我不直接用这个寄存器里的flag=1嘛。
可万万没想到,就在flag=1之后,if之前,来了一次硬件中断,终端回调函数把flag改成0了。
编译器是料不到的,就认为flag=1。这在很多实际情况下是很恐怖的。
不仅仅是中断,插入一段汇编,其他线程改变内存都会引起这类问题。
如果改为volatile int flag=0;,凡是用到这个变量,就会去内存里不怕麻烦地找到它,不再偷懒。
当时我年轻不懂事,一个单片机项目里用中断改变标志位。怎么都不正常,后来哥们让我在标志前面加个volatile就解决了。。。
__WEAK
这不是个关键字,这只是GCC的一个特性。看STM32官方固件库的同学应该没少见到它,但它不是C++里virtual那种虚函数。如果有同名的不带_WEAK前缀的函数,优先使用不带的。
如果用户自定义了,那就使用用户的,如果没有,那就用默认的。这样方便用户自定义一些回调函数、处理函数。
类型
相信大家对C语言的强类型特性印象非常深刻。尤其是printf的格式化输出和复杂指针的类型。
程序不就是数据结构+算法,基本类型则是构成数据结构高楼大厦的一砖一瓦。
char
冥冥之中,我觉得char类型是最神奇的类型。在C语言标准里char的大小是1 Byte,这是不会变的,也就是sizeof(char)无论在哪都是1。但是:
printf("%d\n",sizeof('a'));
输出是多少?是4,一个int的大小!没错,字符常量的类型不是char而是int。
来放松一下。
你平时怎么读“char”?反正我是读了好几年的”差“,后来转念一想,字符的英文是character[ˈkærəktə(r)],那不应该是。。。。其实有三个发音,英文char(煤炭)、car(汽车)、care(关心)都可以。
float
浮点数比整形更贴近实际,也不至于出现除法去尾的情况。要注意的是,计算机的浮点数是分立的,有时候1.30会变成1.299999。比如matlab里查看eps(epsilon)可以得到浮点数的最小分度值。(win64下)
>>eps()
ans=2.2204e-16
结构体、数组、指针
三者的关系可以说是纠缠不清。
刚学C都遇到过,函数返回值可以是一个庞大的结构体,却不能是一个简单的数组。可是,数组类型可以是结构体,结构体的成员也可以包含数组,仅仅是组织方式的区别。
结构体和数组
举例说明一下,现有结构体struct_a,有成员a、b、c三个。
struct {
int a;
int b;
int c;
}struct_a;
//访问a
struct_a.a;
(&struct_a)->a;//千万别写&struct_a->a,->比&优先级高
(*(int*)(&struct_a)+0)//错误!!
//这是错误的,因为结构体的变量之间不保证连续,可能会有填充。
注意最后一种写法,有时候编译器为了对齐,会填充一些地址,导致不连续。不要这样访问结构体成员!
如果是数组呢?我们常用arr[n]这种方式来访问数组成员,”[]“这个符号的用途是把a[b]变成*(a+b)。请结合例子理解一下。
int a[3]={1,2,3};
printf("%d \n",a[0]);
printf("%d \n",*(a+0));
printf("%d \n",0[a]);//输出1
printf("%c",2["abc"])//输出c
数组和指针
把数组传入函数时,有两种写法
int func(int arr[]);
int func(int* arr);
在C里,第一种会自动转化成第二种,所以访问数组本质还是指针。
教你个窍门:
int arr[10];
int* ptr=&arr[-1];
然后就可以从下标1开始用数组ptr[]。
指针
毫无疑问,指针是个麻烦事。比如char *(*(*(a[2])())()是一个包含2个指向返回 指向字符的指针的函数指针的数组,几乎很难看出它到底是数组还是指针。
希望这些要点能帮到你!
优先与[]结合再与*结合
指针类型:把声明中指针名称去掉,就得到了指针的类型。
Int * ptr→int *
Int(* ptr)[3]→int(*)[3]
同时注意结合关系,比如下面这个的名称就是ptr[3],而不是上面的ptr
int *ptr[3]->int *
所指向的类型:去掉指针名称和一个*
int*ptr; : 指针所指向的类型是 int
int ** ptr; : 指针所指向的的类型是 int*
int(*ptr)[3]; : 指针所指向的的类型是 int()[3]
指针赋值时,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符,比如
char* cp;
const char* ccp; ccp=cp;//正确
cp=ccp;//错误
函数
C语言绝不是Python那样自备电池的全能型语言,它是一门中级语言。
标准库函数往往看起来简陋而且有缺陷。
来了解一下吧。
函数调用顺序
int a=f()+g()*h();
这三个函数的执行顺序是不确定的,C标准把选择顺序的权力交给编译器以便针对各个平台进行优化。
可以确定的一点是,乘法优先级高于加法。
参数压栈顺序:从右至左吗?
可能很多人都知道这个考点,函数参数压栈的顺序是从右至左,右边的表达式会先被运行。
重点在后面的问号
C标准对于压栈顺序并没有明确规定,也就是编译器可以修改成从左至右的压栈顺序。
默认的从右至左是为了支持可变参数,用来计算栈的大小。
确保你明白它们的用法、原理和......缺陷
早期的gets()导致了蠕虫病毒,因为它不检查缓冲区是否越界,其实scanf也有这个问题。标准输入输出有许多设计上的细节需要被了解,比如printf使用%来转义%而不是\ , scanf里的\n并不代表等待一个换行符而是读取并抛弃所有空格......
这些设计上的特点可能会在意想不到的地方产生出意想不到的效果。所以,为了程序的健壮性,多多了解它们吧。
预处理
预处理是个很好的想法,增强了程序的可移植性和裁剪性。
不仅仅是常见的#include、#define,它的功能可以非常强大,如果用得好的话。
#include能做些什么?
复制,原封不动的复制。
甚至可以这样:
//str.h
"hu","xiao","an"
//main.c
char arr[3][10]={
#include"str.h"
};
printf(a[1]);//输出xiao
真就是原封不动,但要注意预处理命令是要在一行的开头,独占一行。
如果是#include<>则会首先在标准位置(C语言安装位置)搜寻,#include ""则现在同一文件夹下搜索,找不到再去标准位置搜索。
#define不好吗?
在C++里,用#define来定义常量是不被推荐的,因为#define也仅仅是预处理替换,没有类型检查。
推荐使用const修饰的变量。仁者见仁,两种方法各有特点。
结语
“看似简单”这个描述对于C语言是再恰当不过的。最早的K&R标准只有40页,ANSI C手册则超过了两百页,尽管这样,C语言特性给了编程者极大的自由,衍生出来许多意想不到的用法和Bug。。。
本文整理的内容不过是冰山一角,还有更多的进阶内容等待探索。
周虽旧邦,其命唯新。多次的标准更新已经让C语言不再是教科书里的简陋模样。了解新特征,有利于C语言的实际应用。
希望大家都能够熟练掌握这门传统艺能!
如果有关于编程语言、嵌入式等领域的想法想与我讨论,欢迎各位来找我!
想看文章后续以及更多有关嵌入式、编程语言的分享欢迎关注公众号。

由于C标准的未定义情况较多,交由编译器自己决定的情况非常多,不能保证在任何平台下都是同一结果。
作者能力有限,如有错误或者偏差,恳请各位指正!
欢迎转载,请注明原文地址
老师不讲的C语言知识的更多相关文章
- 【转】R语言知识体系概览
摘要:R语言的知识体系并非语法这么简单,如果都不了R的全貌,何谈学好R语言呢.本文将展示介绍R语言的知识体系结构,并告诉读者如何才能高效地学习R语言. 最近遇到很多的程序员都想转行到数据分析,于是就开 ...
- STM32F4 阿波罗 库函数与C语言知识
先聊一聊: 之前使用32都是用的库函数,但是没有理解为什么那么操作,有很多的文件我也不知道要看哪一个,感觉云里雾里,没有学清楚一件东西的感觉不太好,于是就在前几天一直跟着比较详细的视频学习.开始老师讲 ...
- C语言知识汇总,史上最全面总结,没有之一
C语言基础 C语言学习路线 C语言入门笔记 初识C语言 简单的C程序示例 我们编写的C代码是怎样跑起来的? 简单示例,VS2019调试C语言程序 C语言基础-数据类型 深入理解变量,变量的声明,定义, ...
- Go语言知识查漏补缺|基本数据类型
前言 学习Go半年之后,我决定重新开始阅读<The Go Programing Language>,对书中涉及重点进行全面讲解,这是Go语言知识查漏补缺系列的文章第二篇,前一篇文章则对应书 ...
- 关于C语言知识调查
因为上一篇随笔对这一部分写得不够清楚,因此在这篇做一些补充. 你是怎么学习C语言的? 起初,对于C语言的学习主要是通过老师课堂的教学,完成相关的课后作业.与我的技能相比的话,他们都有一个共同点需要去实 ...
- [Java面试九]脚本语言知识总结.
核心内容概述 1.JavaScript加强,涉及到ECMAScript语法.BOM对象.DOM对象以及事件. 2.Ajax传统编程. 3.jQuery框架,九种选择器为核心学习内容 4.JQuery ...
- 基础语言知识JAVA
1. 总结: JAVA比较重要的博客: http://www.runoob.com/java/java-tutorial.html (JAVA教程) http://blog.csdn.net/ ...
- JavaScript语言知识收藏
接触Web开发也已经有一段时间了,对javascript的认识也比以前有了更加深入的认识了,所以觉得应该整理一下. 一.JavaScript不支持函数(方法)的重载,用一个例子证明如下: functi ...
- C语言知识整理(3):内存管理(详细版)
在计算机系统,特别是嵌入式系统中,内存资源是非常有限的.尤其对于移动端开发者来说,硬件资源的限制使得其在程序设计中首要考虑的问题就是如何有效地管理内存资源.本文是作者在学习C语言内存管理的过程中做的一 ...
随机推荐
- C语言入门--初来乍到
Hi,我是fish-studio,这是我写的第一篇博客,接下来我会以萌新的角度来与大家一起学习C语言,我也不是什么大佬,在我写的教程中会尽量详细的把我遇到的问题写出来,也会结合一些网上的文章进行编写, ...
- Spring Boot 自动配置 源码分析
Spring Boot 最大的特点(亮点)就是自动配置 AutoConfiguration 下面,先说一下 @EnableAutoConfiguration ,然后再看源代码,到底自动配置是怎么配置的 ...
- Mac下安装lightgb并在jupyter中使用
1.先安装cmake和gcc brew install cmake brew install gcc 2.下载后确定自己的gcc版本 cd /usr/local/opt/gcc/lib/gcc/ 看到 ...
- python3 中is和==的区别
is 身份运算符,用来判断对象是否属于同一地址 (python内置函数id() 可以返回对象地址) == 比较运算符,用于判断值是否相同
- 通俗地理解面向服务的架构(SOA)以及微服务之间的关系
SOA是一种软件的应用架构方法,它基于面向对象,但又不是面向对象,整体上是面向服务的架构.SOA由精确的服务定义.松散的构件服务组成,以及业务流程调用等多个方面形成的一整套架构方法. 这话是不是听起来 ...
- 巧用 SVG 滤镜还能制作表情包?
本文将介绍一些使用 SVG feTurbulence 滤镜实现的一些有趣.大胆的的动效. 系列另外两篇: 有意思!强大的 SVG 滤镜 有意思!不规则边框的生成方案 背景 今天在群里面聊天,看到有人发 ...
- 以Aliyun体验机为例,从零搭建LNMPR环境(上)
使用云服务器搭建 Web 运行环境,尤其是搭建常见的 LNMPR(Linux+Nginx+MySQL+PHP+Redis) 环境,对于开发人员是必备的职场基本技能之一.在这里,借着搭建我的" ...
- Logback简介及配置文件logback.xml详解
logback简介及配置文件说明 @author:wangyq @date:2021年3月31日 logback简介 Logback是由log4j创始人设计的另一个开源日志组件,官方网站: htt ...
- Ansible 教程
[注]本文译自:https://www.edureka.co/blog/ansible-tutorial/ 在阅读本文之前,你应该已经知道,Ansible 构成了 DevOps 认证的关键部分,它 ...
- 201871030135-姚辉 实验二 个人项目—《D{0-1} KP》项目报告
项目 内容 课程班级博客链接 课程班级博客链接 这个作业要求链接 这个作业要求链接 我的课程学习目标 (1)掌握软件项目个人开发流程.(2)掌握Github发布软件项目的操作方法. 这个作业在哪些方面 ...