前言:
认识C语言:
什么是C语言,C语言是一门高级编程语言,是从最开始的机器语言(二进制指令)、汇编语言、B语言一路演变改进而来,在计算机历史上有举足轻重的地位,直到现在仍在发挥它的作用。
C语言是有遵循标准的,常见的有C89,C90,C99标准,最初由美国国家标准局制定了一个ANSIC的C语言标准。
C语言初阶知识:第一个程序
初来乍到,当我们来到这世上,一切都是很简洁的,C也是一样,请看第一个程序:
认识新事物先分解来看,这个代码是由主函数main函数、printf()打印函数、return返回值,#include预处理指令组成的一个整体。
如果我们在自己的编译器上按ctrl+f5(编译、链接、运行代码),你会看到这样的一个界面:
它在你屏幕上将hello world输出出来,是不是很帅或觉得神奇?
第一个程序的解析
认识使用main函数。main函数是程序的唯一入口,一个工程只能有一个main函数,使用函数需要小括号()贴在函数名后面,意思是调用该函数,可以认为main函数的接口是操作系统。(绿色部分可以暂时不用理解,不理解问题也不大的,慢慢学就懂啦)
int是一个数据类型,int是整型的英文缩写,int main前面的int的意思是,当main函数调用完(代码都被计算机编译完)后会返回一个整数给到调用main函数的一方(操作系统那一方叫主调函数,主调函数是调用函数的一方,main函数是被调函数)。return是返回的意思,返回一个0(整数),恰好对应int main要求返回一个整型,前后呼应。
printf()函数是C语言提供的一个打印函数,用双引号括起来的部分是要输出的到屏幕上的内容,printf()是C提供给我们的,我们都知道用别人的东西要跟人家打声招呼,经过别人同意你才会去用,这里我们使用#include <stdio.h>来和人家(C标准函数库)打招呼。
print()、return都被包含在代码块{}里,里面可以写代码。ok,解析到这里,你已经懂了一点了,但你肯定还有疑惑的地方,就是写代码的格式问题,究竟什么时候有空格呀?为什么print()、return 0后面都有个分号;,而#include没有,main没有。
这些你先不用担心,看完下面的小小解释,以及本博客后面的代码你就慢慢会这样写格式啦。
先说空格的问题吧,int后面需要空开,不能写成intmain,main与()不能空 开,不能写成main ()。接下来说分号;的使用,C语言中,其实一条语句,完结后都需要加上;来结尾,就像是一句话,有了句号才是完整的,return 0;就是一条完整的语句。
总的来说就是,一个独立的“单词”,你就得与其它”单词”用空格分开,不能混在一起,另外就是为了代码容易看,赏心悦目,有必要空开。
好的说完这些,开始我们的编程之旅吧,启程前,需要你带上你空杯的心态(好好学,戒骄戒躁)。
第一章
1.1变量和常量
变量:顾名思义,会变的量。C语言中变量的创建是由数据类型来创建的。
常量:不会发生改变的量。
1.1.1数据类型
C语言的数据类型有int呀,char(字符类型)、short (int)短整型、long (int)长整型、float(单精度浮点型)、double(双精度浮点型),它们都是C语言的内置类型,但仅靠内置类型是不够的,后面我们还会讲到需要自己定义的自定义类型,比如指针,数组,结构体,字符串这四种,都是非常重要的。
char类型:是用来创建字符变量的,比如‘b’、‘i’、‘t’等用单引号括起来的字母是字母字符,‘5’、'2’、'0'是数字字符,凡是你在键盘上能键入(输入)的键都可以是字符。(char字符类型是属于整型家族的,因为有一个ASCII码表,给每个字符都编了个ASCII码值,这个值是整型,所以char本质上也是整型)
float:是用来表示带小数的,比如3.14、123.4都是浮点数,float和double的区别是在精度和空间大小上的区别,double的精度更高,一般可以表示小数点后十五位。
short、int、long:这三个都是整型,但我们听名字细节就可以知道一些东西,它们虽然都是整型,但用它们来创建整型变量,变量的内存空间大小有差异。
数据类型为什么需要这么多,其实数据类型规定了开辟内存空间的大小,以及以何种视角看待内存中值。简单来讲short (int)类型创建的变量内存空间大小小于int类型为变量开辟的内存空间大小,内存中存的都是二进制,是以整型的视角看待内存中的二进制的。
1.1.2创建变量
了解完数据类型,就可以来创建属于我们自己的变量了,请看代码:
变量的创建是: ”数据类型 变量名 = 值;”,变量名按你需要取名,取名规则是,不要重复,不要和C语言的关键字冲突,像int就是C语言的一个关键字,C语言总共有32个关键字,但一般起名都是正常的,不会冲突。
补充:VS底下写出一个小数如5.5,编译器默认是double类型,写成5.5f就是表示单精度啦。
如果有比较细的细节,我尽量在代码注释里讲出来,因为写在里面会使文章显的冗长,请读者理解理解^.^
注释:双斜杠//后面的就是注释内容。注释掉的内容编译器不会编译,所以不需要的代码可以注销掉,注释还有的作用就是它的本意,在写代码的时候,要养成注释的习惯,让别人好懂,让自己更容易快速理解以前编写时的思路。
双斜杆是C++的注释风格,也是最常用的,C的注释风格是/*.....*/,注释内容可以是连续多行,不过缺点是C的注释风格不支持嵌套,很容易使需要注释掉的代码漏掉。比如:
/*int main()
{
int a = 0;
scanf("%d", &a);*/
return 0;
}*/ //这里就没能把return 给注释掉 我们本意是想头尾匹配的
1.1.3常量分类
常量也有分为字面常量、#define定义的标识符常量、const修饰的常变量、enum枚举常量。
字面常量:字面上的不变的量,比如3.14就是字面上的浮点型常量,当你写出一个常量后,它会自带某个数据类型的属性,再比如3是整型常量。
#define定义的标识符常量:标识符是一个标签的存在,看到雾山五行,你第一反应就是,这是国漫山水画的代表,#define也一样,比如你使用#define Max 100定义Max这个标签,编译器就知道它是100,是一个常量,不要因为是字母Max就以为它是变量。
const修饰的常变量:“常”变量,本质上它还是个变量,前面的常是一个修饰,意味着这个变量有常量的属性,我们知道变量是可变的,而当变量被const修饰后,就不再可变了,请看代码:
enum枚举常量:enum是枚举关键字,枚举是一一列举的意思,这里我们直接看代码容易理解。
这里面的Red、Yellow、Blue都是枚举常量,它们是有值的,依次是0、1、2这样的常量值,这就是枚举常量。这里说到Red不能在main里面被修改,那是不是有其它修改的方法在呢?请看:
get到我的意思了吧 \^.^/,Red在定义的时候被我们赋成100开始,按顺序往下加1,Yellow是101。
讲到这里,还有一点补充的是,区分变量是初始化还是赋值在时间上的不同,请看代码:
那么,创建b没有进行初始化,b里面有没有值,程序员需要掌握的一个重要技巧来了,调试!,按住fn+f10进入调试状态->菜单栏上调试->窗口->监视里任何一个都ok->输入监视对象(变量名),可以看到b的值很奇怪,是个不可猜测的值,我们称之为垃圾值或随机值,这样我们就知道了没初始化的变量会自动放一个随机值在里面,所以变量尽量初始化,不知道就初始化0。
局部变量和全局变量,什么是局部变量,什么是全局变量。请看代码:
“%d”以整型打印a,可以这样理解,把a的值替换到%d的位置。
作用域和生命周期的概念:就局部变量a的作用域来讲,在进入main()函数的代码块时,a变量创建,出代码块时a变量自动销毁,这个代码块就是a变量的作用域,它的生命周期是,在创建是生命周期开始,销毁时生命周期结束。
全局变量b的作用域是整个文件,生命周期是整个工程,因为b在哪都可以用,在一开始就被创建了,只有main()函数调用完才销毁,而main()又是整个工程的代名词,固如上所述。接下来我们来看一个特例:
当局部变量名和全局变量名冲突时,在代码块内的printf("%d", a)是局部变量优先,打印的10,这里还涉及一个问题是,编译器跑不跑得过去的问题,因为有变量名冲突了,是可以跑的,但如果是同时定义在局部或全局的变量名相同,会报警告且跑不过,而这是一个在全局,一个在局部,虽然能跑,但还是不要让变量名冲突,冲突了局部优先。
1.2转义字符和操作符
\n前面有提到是换行的意思,它是一个转义字符,在打印的时候不是打印出\和n而是换行,转义字符有挺多我们慢慢积累,需要注意的是转义字符是算作一个字符不是两个,不要看有两个字符就是两个。
1.2.1认识操作符
算术操作符:+-*/%,加减乘除和取模,前面四个大家耳熟能详,但C语言中我们要讲一些/和%,因为与数学有点不同。
除法求商,取模求余。浮点型不能进行取模运算。如果你想打印一个小数,在除法的任何一侧需要有一个浮点型的数。
%f是以浮点型打印a*1.0/b这个值,同上,我们把值带到%f那就可以了。这里要讲的是如果是a/b的结果是1,而是/的任一边的操作数变成浮点数(有些书也把浮点数说成实数),浮点型最后的结果就有考虑小数了,而不是像整型只考虑整数部分。(变成浮点数的过程是发生了隐式类型转换)
操作数:操作符操作的对象就是操作数,例如a+b,+的操作数是a和b,a是左操作数,b是右操作数,因为+有两个操作数,按操作数数量给+起个名叫双目操作符。
关系操作符:是一组指明大小关系的一组操作符有>、<、>=、<=、==、!=。==是判断是否相等,!=是判断时否不想等,如果判断为真,结果是1,判断为加,结果为0。
单目操作符:!是逻辑反操作符,sizeof是计算变量或类型空间的小的操作符、~是按位(二进制位)取反操作符。
我们可以看到i是0时,!i的结果是1,i是0时,!i的结果是1。!是真变假,假变真的单目操作符。
在计算机中,0表示假,一切非0表示真。!结果默认是0或1,意思就是如果!的操作数是0,则结果为1,如果是非0,结果为0。
二进制与十进制:二进制的基数只有0和1,到2进位。初学者可能不太熟悉,那我们可以类比一下十进制,十进制的基数是0、1、2、3、…… 、到9,遇10进位。八进制也是十六进制也可以这样类比,需要注意的是十六进制从10开始,用字母代替,a字母对应10、b字母对应11、…… 、f字母对应15。
十进制转二进制,二进制转十进制的练习:10110(前面的0省略)转十进制是多少?20(十进制)的二进制是多少。
1.2.2类型开辟内存空间的大小
操作符sizeof(),这里不是函数,虽然有(),等等有解释,sizeof()是用来计算变量或类型的内存大小的,单位是字节(byte),一个字节是8个bit位。所以在前面说i有32个bit位意味着,i是4个字节的空间大小,话不多说,看代码:
char、short、int、long、float、double、分别为变量开辟了1、2、4、4、4、8字节的空间。
补充一下计算机内存单位的知识:最小的计算机单位是bit位,然后字节,kb,mb,gb,tb。进制是1字节=8bit、1kb=1024字节、1mb=1024kb、1gb=1024mb,1tb=1024gb。可能你还没有大小的概念,其实一个bit位的大小形象讲就是存一个1或0,在内存中,要存数字1或0需要的空间就是1个bit位。
因为sizeof a是没问题的,假设它是一个函数,调用函数必须要用(),但sizeof是个操作符,可以不用加(仅限对于变量),看到错误的地方,计算类型的大小时,()是不能省略的。
回归正传,~单目操作符是按位取反的意思,int i = 0; int j = ~i; i的二进制位是:00000000000000000000000000000000 j的二进制位是:11111111111111111111111111111111 如果我们打印j,j的结果会使多少?是很大很大的一个数吗,其实这里就牵涉到有符号数和无符号数,原码、反码、补码的问题了,后面讲,这里的j打印的值是-1。整型在内存中存的是补码,我们写出来的原码,原码按位取反得到反码,反码+1得到补码,int是一个有符号的整型(signed) int,默认的,最高位是符号位,符号位是1表示负,是0表示正。j的补码是全1,打印的时候要转成原码,先减1得到反码,再按位取反得到原码是1000000000000000000000000000001,所以是-1啦。
移位操作符和位操作符,同样的都是对二进制位进行操作。
移位操作符有:<<左移操作符、>>右移操作符。
位操作符有:&按位与、|按位或、^按位异或。不得不说这个和数学的命题里的判断真假有点像。
逻辑操作符:&&逻辑与和||逻辑或,这对操作符不再是对二进制位下手了,看表达式结果,与数学中的并且和或者差不多的意思。
a是10,非0为真,b是5也为真,&&在两个操作数都为真的情况下,默认的结果是1,逻辑与只有两边都为真才是真,如果有假的话(逻辑与和逻辑或会控制表达式求值,待会讲),结果是0。而逻辑或也很简单,只有有一个结果是真,最终结果就是真,注意这里讲的逻辑与和逻辑或区别于位操作符,位操作符是对二进制位的,逻辑操作符是对结果的(5是非0,就是真,0是假),不要混淆了。
为什么说&&和并且像、||和或者像,看代码。
1.2.3逻辑操作符控制表达式求值
我们再补充两个单目操作符,分别是++、--。int a = 10;a++;a的值会变成11。int b = 10;b--;b的值会变成9。这是后置++、--,放在变量后面,还有放在变量前面的是前置++、--,它们的不同在于是先使用还是先调整,请看代码:
补充完后,我们来看一道题,了解逻辑操作符是如何控制表达式求值的。
我们粗略分析一下,a++先使用后调整 也就是0 && ++b && d++; 然后接着算后面的运算,那答案就出错了,是计算机错了吗?不,计算机是不会出错的,我们要谦虚点,要么是自己打出bug了,要么就是自己分析得不对,这里是我们忽略了逻辑操作符有控制表达式求值这个点。
正确的解析是:a++先使用,计算机算出a的值是0(为假)后无论如何结果都是假,它就没继续往下算了,到此终止,这就是短路求值,最后i被赋值0,只有a自增1,其它都不变,最后就是打印出屏幕的结果。
如果把&&换成||,结果又是如何?换成||,当计算机遇到真的时候,就停止往下算,因为只要有真,||表达式的结果一定为真。总结来说:&&操作符遇到假就停止运算,||遇到真就停止运算,它们对表达式的值有控制作用。
赋值操作符:=、+=、-=、*=、/=、&=、|=、^=、>>=、<<=。基本赋值操作符=是来给变量赋值的,还有加等,减等、乘等、除等,是复合赋值操作符,它们其实也很简单,代码一看便知:
强制类型转换用(type),也是一个单目操作符,它的作用改变变量的数据类型,比如,int i = 5;(float)i;就把i的类型从int转换成float了,在C语言中经 常需要使用到强制类型转换(type),学到后面就get到了,当数据类型不相同的两个数进行操作时,编译器会报警告,因为类型不兼容。
条件表达式,逗号表达式,以及另外几个操作符:.(点操作符)、->(箭头)、&(取地址操作符)、*(解引用操作符)我们到恰当的时候都会讲,大家看到这累了吧,休息一会吧~~,我也敲了好久。
我们学了的和提到的单目操作符有:!、~、sizeof()、++、--、(type)、&,*。
还有两个操作符现在可以讲,分别是[](下标引用操作符)是用于数组的、()函数调用操作符,函数调用操作符我们已经比较熟悉了。接下来我们来讲字符串和数组啦。
1.3字符串和数组
1.3.1字符串字面值
C语言有没有字符串类型呢?答案是没有。什么是字符串,由一对双引号“”括起来的一串字符称为字符串字面值也简称为字符串,字符串的结束标志是(字符斜杠0) '\0',为了加快点进度,开始加速,请做好扶稳咯!后面多用代码来解释啦:
补充:单独的一个“hello, world”这样一个字符串,叫做字符串字面值,在讲常量的时候,我们说3是一个常量字面值,对的,由双引号括起来的单独的一串字符是不能被修改的。
arr3打印出bit是因为有斜杠0这个元素在数组里面,arr1打印效果与arr3一样,其实是因为“字符串”,双引号引起的字符串里隐藏了一个斜杠0,而arr2就没有斜杠0了,所以在打印的时候会一直打印下去直到遇到斜杠0才停止下来,期间我们只知道会打印bit、后面的值是随机值我们预料不到。
1.3.2数组的创建、初始化和访问
创建数组就是确定元素类型、起个数组名、[]看元素个数。数组初始化用{},每个元素用逗号隔开,初始化是在定义的时候赋值。
访问数组是用下标操作符[],数组的每个元素都有对应的下标,下标从0开始。
数组的下标的最大值是元素个数减一
1.4选择语句和循环语句
1.4.1if选择语句
在生活中,我们总是有很多选择,简单到吃晚饭的时候,是选择吃外卖还是在学校食堂吃,当我们做出一个判断后,吃什么就确定下来了。意思就是说计算机在遇到选择结构的时候,会根据判断结果,选择性地执行。
if语句是一个选择语句,我们要学单独一个if、if和else、if实现多分支与switch case语句实现多分支。
{}代码块可以包含多条语句,也使代码更容易看懂。
回忆一下,计算机中,0是假,一切非0为真。
1.4.2while循环语句
日复一日,年复一年。不管风吹雨打,程序员都在敲代码提升自己,只因热爱和实现自己的价值。请看代码:
心中有大厂,行稳志远!循环我们先讲这个while,后面会讲do while语句,for语句,for是最常用的一个,也会讲break,continue关键字的作用。敬请期待。
1.5函数与常见关键字
1.5.1函数基本了解
函数是程序中非常重要的一个模块,子程序是对函数的一个描述,函数是由函数头,函数体组成的,函数头包括函数返回类型、函数名、参数列表(接收主调函数传过来的参数)。
函数是一个方法,它解决的问题是当我们需要将两个数相加时,我们将加法封装成一个函数,通过调用函数并把参数传给函数,它就帮我们实现加法,大大简洁了代码。
函数是一个相对独立的一段代码,它就是单纯帮你实现一个功能,这样就好,我们追求的函数是高内聚低耦合,一个函数实现自己的功能,尽量不要与外面的代码有重合的。
1.5.2C语言中32个关键字
关键字是C语言固有的,不能修改,也不能自创。
auto关键字:每个局部变量都有被auto修饰,auto int a = 0;正因为每个都有,所以省略掉了,它是自动的意思,自动创建,自动销毁。
break是跳出switch case语句和循环语句用的。
continue是循环语句使用的后面会讲。
default(默认):default字句在switch case语句中是一种默认的情况,当我们的值不符合case中的任何一个语句时,走的就是default语句,记得加上,养成好习惯。
extern关键字:用于声明外部变量,外部函数。
我们能不能使用另一个原文件里定义的变量呢?使用另一个文件里定义的函数呢?联系一下库函数要使用需要使用#include包含头文件,其实要使用变量,函数都需要声明,告诉编译器有这么个东西存在。
变量glo_bal定义在Add文件中,想在test.c里使用需要用extern来声明。
函数也一样,extern 函数返回类型 函数名 (函数参数类型),声明就可以用啦。
讲到extern就不得不提static关键字:
1.static修饰局部变量,延长局部变量的生命周期。
实际上,当我们调试的时候,循环10次中static语句只执行一次,后面就都跳过了,没执行。
a静态变量只能在main{}里使用。
2.static修饰全局变量,改变了全局变量的作用范围。
全局变量可以通过extern声明在其它文件中使用,但在定义的时候 static int a = 10; 它就只能在定义它的文件里使用。
3.static修饰函数,改变了函数的链接性。
函数同上,static int Add(int x,int y)后,Add只具有内链性,不具有外链性,因此如果函数是重要的不希望被人使用的可以加上这个静态关键字。
register:寄存器关键字,目前很少使用了,它的加在变量前面是在告诉编译器,把这个变量存到寄存器中去,但存不存是编译器根据这个变量的使用情况存的。
struct结构体关键字(晚点讲)和union联合体关键字(C进阶讲),结构体非常重要
typedef关键字:类型重定义,给数据类型起个小名,叫小名也是在叫对应的数据类型。
1.6指针和结构体
1.6.1指针的本质
指针本质上是一个地址,日常我们口中说的指针是指针变量,用来存放地址的变量。在学习指针之前我们有必要了解地址是怎么产生的。
我们知道电脑有32位或64位机器,那么就有32根或64根地址线,每根地址线都能产生正电或负电,转换成数字信号就是0或1。那么总共有多少个组合呢,答案是2的32次方(32位),我们把每一个编号当作一个地址编号对应一个内存单元,那么问题又来了,一个内存单元大小是多少合适呢?1个bit位?还是一个字节?
1.6.2指针变量的创建
当我们在创建变量的时候,变量存在内存中,它的内存编号是唯一的,我们希望能用一个变量存放它的地址,因为有一天我要通过存放的地址找到它,对变量进行操作。
pa就是指针变量,int*是指针的类型。指针占用多少字节的空间,我们仔细一想,一个地址是由32个0或1组合成的32位,欲想存放一个地址,那是不是得4个字节才能放得下,是的,在32位机器下,指针类型创建的指针变量的大小就是4个字节,64位下是8个字节。
再举个例子:char ch = ‘w’; 想要一个指针指向ch char* pc = &c; p是pointer指针的意思。
1.6.3结构体的声明,访问
结构体是用来描述复杂对象的好东西,结构体是由成员变量组成的,成员变量可以不止是一种数据类型。
看到这里,相信你也很累了吧,看完这篇对C语言就有大概的一个了解,这只是一个轮廓。
希望你能有所收获,求点赞,你的点赞是我更新的动力。我的CSDN现在主要更新那边啦。