好久不见啦朋友们。
前天参加了软件设计师考试,说实话,有点emmm,但是我也发现很多基础已经忘得差不多了,这就是传说中的手生了吗?
手生到什么地步?前天晚上帮我朋友改代码,甚至连scanf输入double类型数据用什么方式我都想不起来了。
所以,我就整理了一下我自己的学习路线。
江东子弟多才俊,卷土重来未可知!
拿着《C Primer Plus》梳理了一遍,发现还真的有不少细节平时没有注意到,或者是没有刻意的去注意。
(写代码不写文档,拖出去打屎)
最开始接触到代码文档不知道是什么时候了,但是让我想写代码文档绝对是在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、目前我们所学习的各种语言,基本都离不开C语言的影子,所谓万变不离其宗,就是这个道理。
2、C语言快,不接受反驳。不要说什么汇编更快,写个我看看。
3、可移植性强。就拿嵌入式这一块来说,哪个语言能轻松移植?
4、细节把控到位。C语言给了程序员极大的细节操作权限,连内存分配都给了。只是我们自己把握不住而已,C语言的水太深了。
别说输入输出了,不包任何头文件,我不知道还能写什么C代码。
为什么要这样呢?像Python那样都内置了不好吗?
这也是C语言为什么能做嵌入式,而Python做不了的一个重要原因。
C语言的一个基本设计原则是避免不必要的成分。
我们不可否认,并不是所有项目都需要输入输出的。
儿童节快乐呀各位
不知道有多少小伙伴接触过这个 void main()
,反正我刚开始学C的时候,那个老师是教我们写这个的,当时就跟我们说,如果没有什么返回值,就写这个就好啦。
后来,遇到我的授业恩师的时候,他叫我们说,不要写void main()
了,打开编辑器,写完头文件依赖之后,先把下面这个框架写上:
int main(){
return 0;
}
就算没有什么返回值,也写一个return 0;
,错不了的。
有些编译器会允许void main()
的形式,但是还没有任何标准考虑接受它,所以编译器可以不接受这种形式,这就是一个在平台移植中存在的一个隐患。
多写一行return 0;
很难吗?
有的小伙伴可能不知道,在循环语句、分支语句中,如果代码块儿只有一行的情况下(或者循环下面只有一个分支语句),则那个花括号是可写可不写的。
比方说:
while(1)
printf('1\n')
那这个花括号写不写呢?
不写会有什么好处?
1、代码看起来行数会短一丢丢
2、少写两个括号
不写有什么坏处?
当下基本不会有什么坏处,当下咱的头脑坑定是清醒的,知道为什么不写。
但是修改代码的时候呢?如果要在这种循环下动刀,却又忽略了这个括号呢?
我前两天就遇到了,浪费我五分钟去调试。
所以啊,又不是说什么很必要的,为什么不写?写两个括号会累着?
0==a
与 a==0
这个又属于那种举手之劳,但是暴雷的时候不知死活的问题了。
一般这个细节老师在讲分支循环的时候都会说吧,如果少写了一个等号,0=a是会报错的,但是a=0是会崩溃的。
不过现在还好,有的编辑器就会警告,前提是要使用够好的编辑器,碧如VS。
像我以前用TXT编程的时候,这个问题就只能靠自己去挖掘了。
细节之处见真章。
这就涉及到字符和字符串的概念了。
这也是一段时间不敲C代码会很容易忘掉的一个点。
首先,T如果经过赋值,它既是一个变量。否则它什么也不是。
其次,‘T’是一个字符,一个char,不是一个字符串。
紧接着,“T”是一个字符串,不是一个char。
什么是字符串,可以理解为char的数组,不过在字符串结尾的时候会带上一个‘\0’。
不要再觉得这三个很神秘了,它们只不过是修饰词而已了。
short,可能占用比int类型更少的空间,用于仅需小数值的场合,可以简写为short。同int 类型一样,是一种有符号类型。
long,可能占用比int类型更大的空间,用于使用大数值的场合,可简写为long。同int类型一样,是一种有符号类型。
long long,可能占用比long更大的空间,用于更大数值的场合,可简写为long long,同int类型一样,是一种有符号类型。
unsigned,用于只使用非负的场合。将原本分配给负数的空间大小都分配给了正数。
%d —— 以带符号的十进制形式输出整数
%o —— 以无符号的八进制形式输出整数
%x —— 以无符号的十六进制形式输出整数
%u —— 以无符号的十进制形式输出整数
%c —— 以字符形式输出单个字符
%s —— 输出字符串
%f —— 以小数点形式输出单、双精度实数
%e —— 以标准指数形式输出单、双精度实数
%g —— 选用输出宽度较小的格式输出实数
如果是打印short,用%u,打印long,用%ld,以免在移植过程中造成不必要的麻烦。
和读取单个字符不同,读取字符串的时候,是不需要加上&符号的。
其实上网一搜就有了,但是有的比较重要的还是要记一下的。
ESC -- 27
0 -- 48
A -- 65
a -- 97
对于浮点数的比较,只能用<和>,舍入误差可能造成两个逻辑上应该相等的数不相等。。
printf()什么时候真正把输出传送给屏幕?printf()语句将输出传送给一个被称为缓冲区的中介存储区域,缓冲区中的数据再不断地被传送给函数。
标准C规定在以下情况下将缓冲区内容输送给屏幕:
1、缓冲区满
2、遇到换行符
3、后面跟了一个scanf语句
可能在平时看来没有什么关系,但是我们在写服务器代码的时候就会有这种问题出来,有时候会导致消息队列被卡死,有时候会导致数据无法及时的被排出。
这里拓展一下缓冲区,为什么需要缓冲区?
首先,将若干个字符作为一个块传输比逐个发送这些字符耗费的时间少。
其次,如果输入有误,就可以使用回删来更正错误。
当最终按下回车简单的时候,就可以发送正确的输入。
缓冲分为两类,完全缓冲I/O和行缓冲I/O,对完全缓冲输入来说,缓冲区满时被清空,这种类型的缓冲常出现在文件传输中。缓冲区的大小取决于操作系统。
对于行缓冲来说,遇到一个换行符就将清空缓冲区,键盘输入是标准的行缓冲,因此按下回车键将清空缓冲区。
我就不吭声儿,哪个写C/C++的朋友没有遇到过这个问题。
越界。
一个潜在的问题是:出于执行速度考虑,C并不检查您是否使用了正确的数值下标,当程序运行的时候,这些语句把数据放在可能被其他数据使用到的位置上,因而可能破坏程序的结果,甚至使得程序结果崩溃。
在使用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 *a,这时候要使用a,要手动分配空间。
而且,当我们使用sizeof(a)的时候,得出的结果是指针的大小,这是一个坑。
要获取a的大小,使用len()函数。
这里把后面一个问题一并写进来吧,
结构体中是应该放 char* 还是char[] 呢?
要知道,结构体不为字符串分配任何存储空间,所以自己掂量掂量。
如果没有什么特殊需求,还是放char[],如果要定制,那就char*。
反正这俩我都试过,一个是定长包,相对简单,一个是不定长包,虽然困难了点,可以克服。
getchar的用法
getchar()是stdio.h中的库函数,它的作用是从stdin流中读入一个字符,也就是说,如果stdin有数据的话不用输入它就可以直接读取了,第一次getchar()时,确实需要人工的输入,但是如果你输了多个字符,以后的getchar()再执行时就会直接从缓冲区中读取了。
实际上是 输入设备->内存缓冲区->程序getchar
putchar的用法
(1)输出:putchar函数只能用于单个字符的输出,向终端输出一个字符,且一次只能输出一个字符。
(2)格式:对于变量来说,格式为:putchar(ch);对于常量来说,格式为:putchar(‘ch’),对于转义字符来说,格式为:putchar(’\n’)。
字符串的处理一直是很重要的问题,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()一样了
为什么我觉得这个部分值得这么多星星呢?可能有的朋友觉得不值。
写几个项目,再优化,就知道了。
“需要知道”原则,类似于“单一职责原则”,尽可能保持每个函数内部工作对该函数的私密性,只共享那些需要共享的变量。
一个C变量具有以下链接之一:外部链接、内部链接或空链接。
具有代码块作用域或者函数原型作用域的变量具有空链接,意味着它们是由其定义所在的代码块或函数原型所私有。
重点来了:具有文件作用域的变量可能有内部链接或外部链接,一个具有外部链接的变量可以在一个多文件的程序的任何地方使用,一个具有内部链接的变量可以在一个文件的任何地方使用。
那怎样知道一个文件作用域变量具有内部链接还是外部链接?可以看看在外部定义中是否使用了存储类说明符static。
把变量的定义声明放在所有函数之外,即创建了一个外部变量。为了使程序更加清晰,可以在使用外部变量的函数中通过使用extern关键字来再次声明它。
如果变量是在别的文件中定义的,那么使用extern来声明该变量就是必须的。
在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只有一个元素,argv0指向输入的程序路径及名称:./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++预编译/条件编译指令 #ifdef,#ifndef,#endif,#define,…
开发成长之路(3)-- C语言从入门到开发(讲明白指针和引用,链表很难吗?)
精品专栏打造计划