重温C语言,这三十多个细节你把握住了?

在这里插入图片描述

文章目录

前言

好久不见啦朋友们。

前天参加了软件设计师考试,说实话,有点emmm,但是我也发现很多基础已经忘得差不多了,这就是传说中的手生了吗?
手生到什么地步?前天晚上帮我朋友改代码,甚至连scanf输入double类型数据用什么方式我都想不起来了。

所以,我就整理了一下我自己的学习路线。
江东子弟多才俊,卷土重来未可知!

拿着《C Primer Plus》梳理了一遍,发现还真的有不少细节平时没有注意到,或者是没有刻意的去注意。


基本篇

1、编写代码文档

难度指数:1颗星 / 细节指数:5颗星 / 重要指数:5颗星

(写代码不写文档,拖出去打屎)

最开始接触到代码文档不知道是什么时候了,但是让我想写代码文档绝对是在pycharm上。
很方便,打三个引号,一个回车,什么都给你准备好了。
然后我就想在VS上也找到类似的功能。起初没找到,后来误打误撞试出来了:

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="l1"></param>
        /// <param name="l2"></param>
        /// <returns></returns>

呐,看明白不?画三个杠,不用回车,两个<summary>之间写你的函数功能描述,</param>之间写你的参数释义,也可以写在</param>后面,return我就不多说啥了吧。


C语言相对其他语言的优势

难度指数:1颗星 / 细节指数:1颗星 / 重要指数:3颗星

(连C语言的优势都不清楚,学来干什么?)

1、目前我们所学习的各种语言,基本都离不开C语言的影子,所谓万变不离其宗,就是这个道理。
2、C语言快,不接受反驳。不要说什么汇编更快,写个我看看。
3、可移植性强。就拿嵌入式这一块来说,哪个语言能轻松移植?
4、细节把控到位。C语言给了程序员极大的细节操作权限,连内存分配都给了。只是我们自己把握不住而已,C语言的水太深了。


C语言为什么不内置输入输出语句?

难度指数:2颗星 / 细节指数:4颗星 / 重要指数:3颗星

别说输入输出了,不包任何头文件,我不知道还能写什么C代码。

为什么要这样呢?像Python那样都内置了不好吗?

这也是C语言为什么能做嵌入式,而Python做不了的一个重要原因。

C语言的一个基本设计原则是避免不必要的成分

我们不可否认,并不是所有项目都需要输入输出的。


儿童节快乐呀各位


int main() 与 void main()的区别

难度指数:1颗星 / 细节指数:3颗星 / 重要指数:2颗星

不知道有多少小伙伴接触过这个 void main(),反正我刚开始学C的时候,那个老师是教我们写这个的,当时就跟我们说,如果没有什么返回值,就写这个就好啦。
后来,遇到我的授业恩师的时候,他叫我们说,不要写void main()了,打开编辑器,写完头文件依赖之后,先把下面这个框架写上:

int main(){
	
	return 0;
}

就算没有什么返回值,也写一个return 0;,错不了的。

有些编译器会允许void main()的形式,但是还没有任何标准考虑接受它,所以编译器可以不接受这种形式,这就是一个在平台移植中存在的一个隐患。

多写一行return 0;很难吗?


可写可不写的花括号,凭什么要我写?

难度指数:1颗星 / 细节指数:2颗星 / 重要指数:2颗星

有的小伙伴可能不知道,在循环语句、分支语句中,如果代码块儿只有一行的情况下(或者循环下面只有一个分支语句),则那个花括号是可写可不写的。

比方说:

while(1)
	printf('1\n')

那这个花括号写不写呢?
不写会有什么好处?
1、代码看起来行数会短一丢丢
2、少写两个括号

不写有什么坏处?
当下基本不会有什么坏处,当下咱的头脑坑定是清醒的,知道为什么不写。
但是修改代码的时候呢?如果要在这种循环下动刀,却又忽略了这个括号呢?
我前两天就遇到了,浪费我五分钟去调试。

所以啊,又不是说什么很必要的,为什么不写?写两个括号会累着?


0==aa==0

难度指数:1颗星 / 细节指数:4颗星 / 重要指数:2颗星

这个又属于那种举手之劳,但是暴雷的时候不知死活的问题了。

一般这个细节老师在讲分支循环的时候都会说吧,如果少写了一个等号,0=a是会报错的,但是a=0是会崩溃的。

不过现在还好,有的编辑器就会警告,前提是要使用够好的编辑器,碧如VS。
像我以前用TXT编程的时候,这个问题就只能靠自己去挖掘了。

细节之处见真章。


T、‘T’、“T”的区别

难度指数:3颗星 / 细节指数:2颗星 / 重要指数:4颗星

这就涉及到字符和字符串的概念了。
这也是一段时间不敲C代码会很容易忘掉的一个点。

首先,T如果经过赋值,它既是一个变量。否则它什么也不是。
其次,‘T’是一个字符,一个char,不是一个字符串。
紧接着,“T”是一个字符串,不是一个char。

什么是字符串,可以理解为char的数组,不过在字符串结尾的时候会带上一个‘\0’。


short、long、unsigned

难度指数:2颗星 / 细节指数:2颗星 / 重要指数:1颗星

不要再觉得这三个很神秘了,它们只不过是修饰词而已了。

short,可能占用比int类型更少的空间,用于仅需小数值的场合,可以简写为short。同int 类型一样,是一种有符号类型。

long,可能占用比int类型更大的空间,用于使用大数值的场合,可简写为long。同int类型一样,是一种有符号类型。

long long,可能占用比long更大的空间,用于更大数值的场合,可简写为long long,同int类型一样,是一种有符号类型。

unsigned,用于只使用非负的场合。将原本分配给负数的空间大小都分配给了正数。


标准输入输出中的占位符

难度指数:2颗星 / 细节指数:2颗星 / 重要指数:3颗星

%d —— 以带符号的十进制形式输出整数
%o —— 以无符号的八进制形式输出整数
%x —— 以无符号的十六进制形式输出整数
%u —— 以无符号的十进制形式输出整数
%c —— 以字符形式输出单个字符
%s —— 输出字符串
%f —— 以小数点形式输出单、双精度实数
%e —— 以标准指数形式输出单、双精度实数
%g —— 选用输出宽度较小的格式输出实数

如果是打印short,用%u,打印long,用%ld,以免在移植过程中造成不必要的麻烦。


scanf读取字符串

和读取单个字符不同,读取字符串的时候,是不需要加上&符号的。


常用ascii码

难度指数:1颗星 / 细节指数:3颗星 / 重要指数:4颗星

其实上网一搜就有了,但是有的比较重要的还是要记一下的。

ESC -- 27
0 -- 48
A -- 65
a -- 97

浮点数的比较大小

难度指数:1颗星 / 细节指数:2颗星 / 重要指数:2颗星

对于浮点数的比较,只能用<和>,舍入误差可能造成两个逻辑上应该相等的数不相等。。


在这里插入图片描述


提升篇

刷新输出

难度指数:2颗星 / 细节指数:3颗星 / 重要指数:4颗星

printf()什么时候真正把输出传送给屏幕?printf()语句将输出传送给一个被称为缓冲区的中介存储区域,缓冲区中的数据再不断地被传送给函数。

标准C规定在以下情况下将缓冲区内容输送给屏幕:
1、缓冲区满
2、遇到换行符
3、后面跟了一个scanf语句

可能在平时看来没有什么关系,但是我们在写服务器代码的时候就会有这种问题出来,有时候会导致消息队列被卡死,有时候会导致数据无法及时的被排出。


这里拓展一下缓冲区,为什么需要缓冲区?
首先,将若干个字符作为一个块传输比逐个发送这些字符耗费的时间少。
其次,如果输入有误,就可以使用回删来更正错误。
当最终按下回车简单的时候,就可以发送正确的输入。

缓冲分为两类,完全缓冲I/O和行缓冲I/O,对完全缓冲输入来说,缓冲区满时被清空,这种类型的缓冲常出现在文件传输中。缓冲区的大小取决于操作系统。
对于行缓冲来说,遇到一个换行符就将清空缓冲区,键盘输入是标准的行缓冲,因此按下回车键将清空缓冲区。


out of range

难度指数:4颗星 / 细节指数:5颗星 / 重要指数:5颗星

我就不吭声儿,哪个写C/C++的朋友没有遇到过这个问题。

越界。

一个潜在的问题是:出于执行速度考虑,C并不检查您是否使用了正确的数值下标,当程序运行的时候,这些语句把数据放在可能被其他数据使用到的位置上,因而可能破坏程序的结果,甚至使得程序结果崩溃。


scanf 和 scanf_s

难度指数:3颗星 / 细节指数:1颗星 / 重要指数:2颗星

在使用VS的时候,会发现编译器不通过scanf,给的理由是不安全、即使已经#include<stdio.h>

这时候,它就会推荐我们去使用scanf_s。

解决方法一:
点击项目->项目属性,点开属性页面
在这里插入图片描述

点击C/C++ -> 预处理器 -> 预处理器定义 -> 点击右侧的下拉列表 -> 点击下拉列表里的<编辑>
在这里插入图片描述

在预处理器定义中添加字段 _CRT_SECURE_NO_WARNINGS
在这里插入图片描述

然后点击确定,就可以使用scanf了

但是仅限于这一个项目,其他的项目还是不能使用,因此需要对所有要使用scanf的项目进行逐个修改


方法二:使用scanf_s

scanf()不会检查输入边界,可能造成数据溢出。

scanf_s()会进行边界检查。

因为带“_s”后缀的函数是为了让原版函数更安全,传入一个和参数有关的大小值,避免引用到不存在的元素,防止hacker利用原版的不安全性(漏洞)黑掉系统。

scanf_s()参数与scanf()不同:
例如scanf_s(“%s”,&name,n),整形n为name类型的大小,如果name是数组,那n就是该数组的大小。


char* 与 char[n]

难度指数:1颗星 / 细节指数:3颗星 / 重要指数:3颗星

这二者,最大的区别就在于,一个是动态空间分配,一个是静态空间分配。

如果说,使用了char *a,这时候要使用a,要手动分配空间。

而且,当我们使用sizeof(a)的时候,得出的结果是指针的大小,这是一个坑。

要获取a的大小,使用len()函数。


这里把后面一个问题一并写进来吧,

结构体中是应该放 char* 还是char[] 呢?
要知道,结构体不为字符串分配任何存储空间,所以自己掂量掂量。
如果没有什么特殊需求,还是放char[],如果要定制,那就char*。
反正这俩我都试过,一个是定长包,相对简单,一个是不定长包,虽然困难了点,可以克服。


getchar() 与 putchar()

难度指数:3颗星 / 细节指数:2颗星 / 重要指数:2颗星

getchar的用法

getchar()是stdio.h中的库函数,它的作用是从stdin流中读入一个字符,也就是说,如果stdin有数据的话不用输入它就可以直接读取了,第一次getchar()时,确实需要人工的输入,但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读取了。
实际上是 输入设备->内存缓冲区->程序getchar

putchar的用法

(1)输出:putchar函数只能用于单个字符的输出,向终端输出一个字符,且一次只能输出一个字符。
(2)格式:对于变量来说,格式为:putchar(ch);对于常量来说,格式为:putchar(‘ch’),对于转义字符来说,格式为:putchar(’\n’)。


sprintf()做字符串拼接

难度指数:2颗星 / 细节指数:2颗星 / 重要指数:4颗星

字符串的处理一直是很重要的问题,C语言中的字符串拼接又不像Python里面直接一个加号就能解决的。
那么要怎么处理呢?这里采用 sprintf() 的方式来做这件事情。

int sprintf(char *str, const char *format, ...);

参数释义:

str -- 这是指向一个字符数组的指针,该数组存储了 C 字符串。
format -- 这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。format 标签属性是 %[flags][width][.precision][length]specifier,具体讲解如下:

不多说,上案例:

char s[40]; 
sprintf(s,"%s%d%c","test",1,'2'); /*第一个参数就是指向要写入的那个字符串的指针,剩下的就和printf()一样了

设计原则:“需要知道”原则

难度指数:3颗星 / 细节指数:4颗星 / 重要指数:4颗星

为什么我觉得这个部分值得这么多星星呢?可能有的朋友觉得不值。
写几个项目,再优化,就知道了。

“需要知道”原则,类似于“单一职责原则”,尽可能保持每个函数内部工作对该函数的私密性,只共享那些需要共享的变量。


作用域·链接

难度指数:2颗星 / 细节指数:1颗星 / 重要指数:2颗星

一个C变量具有以下链接之一:外部链接、内部链接或空链接。
具有代码块作用域或者函数原型作用域的变量具有空链接,意味着它们是由其定义所在的代码块或函数原型所私有。

重点来了:具有文件作用域的变量可能有内部链接或外部链接,一个具有外部链接的变量可以在一个多文件的程序的任何地方使用,一个具有内部链接的变量可以在一个文件的任何地方使用。

那怎样知道一个文件作用域变量具有内部链接还是外部链接?可以看看在外部定义中是否使用了存储类说明符static。


extern

难度指数:2颗星 / 细节指数:2颗星 / 重要指数:3颗星

把变量的定义声明放在所有函数之外,即创建了一个外部变量。为了使程序更加清晰,可以在使用外部变量的函数中通过使用extern关键字来再次声明它。

如果变量是在别的文件中定义的,那么使用extern来声明该变量就是必须的。


argc 和argv

难度指数:2颗星 / 细节指数:2颗星 / 重要指数:3颗星

在Linux底下编程的时候,经常会看到如下的一行代码:

int main(int argc,char*argv[]){}

有时候,这个argv还会在main函数实现中被用到,那么就会有小伙伴不知道是干嘛用的,或者说知道是干嘛用的,不知道怎么用。

我也困惑过,所以写下来。

main(int argc,char *argv[ ])

argv为指针的指针

argc为整数

char **argv or: char *argv[] or: char argv[][]

假设程序的名称为CX,

当只输入CX,则由操作系统传来的参数为:

argc=1,表示只有一程序名称。

argc只有一个元素,argv[0]指向输入的程序路径及名称:./CX

当输入==./CX CanShu_1==,有一个参数,则由操作系统传来的参数为:argc=2,表示除了程序名外还有一个参数。

argv[0]指向输入的程序路径及名称。
argv[1]指向参数para_1字符串。

当输入==./CX CanShu_1 CanShu_2== 有2个参数,则由操作系统传来的参数为:argc=3,表示除了程序名外还有2个参数。

argv[0]指向输入的程序路径及名称。

argv[1]指向参数para_1字符串。

argv[2]指向参数para_2字符串。

以此类推.


C/C++预编译/条件编译指令

难度指数:2颗星 / 细节指数:2颗星 / 重要指数:3颗星

实在不想再长篇大论了,偷个懒吧:讲通C/C++预编译/条件编译指令 #ifdef,#ifndef,#endif,#define,…


指针篇

难度指数:5颗星 / 细节指数:5颗星 / 重要指数:5颗星

开发成长之路(3)-- C语言从入门到开发(讲明白指针和引用,链表很难吗?)

精品专栏打造计划


  • 390
    点赞
  • 1504
    收藏
  • 打赏
    打赏
  • 79
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:代码科技 设计师:Amelia_0503 返回首页
评论 79

打赏作者

看,未来

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值