C基础语法速览 叠甲:以下文章主要是依靠我的实际编码学习中总结出来的经验之谈,求逻辑自洽,不能百分百保证正确,有错误、未定义、不合适的内容请尽情指出! 文章目录 1.数据类型 1.1.数据类型的常见分类 1.2.数据类型的符号修饰 1.3.数据类型的存储范围 2.变量常量 2.1.变量 2.1.1.变量的创建 2.1.2.变量的作用域 2.1.3.变量的生命周期 2.2.常量 2.2.1.字面常量 2.2.2.常变量 2.2.3.宏常量 2.2.4.枚举常量 3.字符基础 3.1.字符 3.2.字符串 3.3.转义字符 3.4.字符编码 4.代码注释 4.1.注释作用 4.2.注释方式 5.逻辑语句 5.1.选择语句 5.1.if 语句 5.1.2.switch 语句 5.2.循环语句 5.2.1.while 5.2.2.do-while 5.2.3.for 5.3.goto 语句 6.函数 5.1.自定义函数 5.1.1.函数结构 5.1.2.函数分类 5.1.3.函数作用 5.2.一些库函数 5.2.1.[scanf()](https://legacy.cplusplus.com/reference/cstdio/scanf/) 5.2.2.[printf()](https://legacy.cplusplus.com/reference/cstdio/printf/) 5.2.3.[strlen()](https://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen) 5.2.4.[rand()](https://legacy.cplusplus.com/reference/cstdlib/rand/) 和 [srand()](https://legacy.cplusplus.com/reference/cstdlib/srand/) 和 [time()](https://legacy.cplusplus.com/reference/ctime/time/?kw=time) 7.数组 7.1.一维数组 7.1.1.数组的创建初始 7.1.2.数组的下标访问 7.1.3.数组的存储方式 7.1.4.数组的素个数 7.2.高维数组 7.3.变长数组 8.操作符 8.1.算术操作符 8.2.移位操作符 8.3.赋值操作符 8.4.符号操作符 8.5.关系操作符 8.6.逻辑操作符 8.7.条件操作符 8.8.逗号操作符 8.9.地址操作符 8.10.结构操作符 8.11.自增操作符 8.12.其他操作符 8.13.优先级和结合方向 9.关键字 9.1.typedef 9.2.extern 9.3.static 9.3.1.用在局部变量上 9.3.2.用在全局变量上 9.3.3.用在函数上 10.宏 10.1.宏常量 10.2.宏体/宏函数 11.指针 11.1.内存是什么 11.2.编址如何编 11.3.指针怎么用 11.4.指针的大小 12.结构体 13.编码素养 13.1.编码习惯 13.2.编码自救 补充:这里我简单介绍 语言的基础语法,旨在让您快速上手 语言,更多细节还会在后续展开。 1.数据类型 首先,您需要了解一下数据类型,以便处理程序中的数据。 1.1.数据类型的常见分类 数据类型主要分为以下几类关键字: 补充:关键字又可以叫“保留字”,如果把编程语言理解为一门类似英语、汉语的语言,那么关键字就相当于是编程语言的“单词”,通过正确的语法来组合不同的关键字可以形成一个由编程语言书写的程序。 为什么需要数据类型关键字呢?不妨先从为何需要编写代码考虑起。 我们为什么写代码 是为了解决生活的问题 而生活中每个物体都会携带信息 (例如: 描述一个学生可以用年龄、性别、学号、身份证…这些信息来描述) 需要信息转化为数据,利用数据来描述信息 (例如: 假定0代表男、1代表女…) 不同的数据可以分成不同的类别(字符型、整数、小数类型…) 因此有了数据类型的产生 使用数据类型是为了能更有力针对数据的类型来描述对象 通过数据类型对物体的信息做分类存储 进而解决实际问题 因此,数据类型可以帮助我们创建容器,也就是所谓的变量。 编程语言中的变量可以用来存储数据,数据为何需要被存储起来? 很简单,一旦有数据不是立刻就需要被计算机作输出的,可能还需要对变量内部的数据做一些其他的计算处理,等到处理完后才可以被计算机输出。 而使用 容器/变量 就是一种很好的选择,不同数据类型关键字创建出来的空间大小不同,内部存储规则也有可能不同,因此:根据数据的类型来择取数据类型关键字相当重要。 那为什么需要使用这么多数据类型呢?是因为使用不同的类型可以较为精确把控所用空间的大小,根据数据的大小进行数据类型的选择,可以节省很多空间。并且计算机的读取和处理效率也能提高,但相应的错误也可能会变多(比如不小心发生“数据溢出”的现象)。 补充:实际上在之后的编码中,使用整型类关键字居多(尤其是 类型),原因您以后就会明白。 吐槽:编程语言这里,数据类型实际上通常有两个作用。 作为创建变量时的关键字(后面会提怎么创建一个变量) 作为描述某一个变量或常量的分类(比如通常会有程序员认为 是 类型的, 是 类型的,某某个变量是字符类型的…) 1.2.数据类型的符号修饰 而数据类型还有符号之分。 如果是有符号的数据(即存储的数据有可能具有负值),那么可以给类型前加上关键字 ,表明使用该数据类型创建的容器/变量不仅可以存储零和正值的数据,也可以存储负值的数据(例如 、)。但是这是默认行为,即使您不写编译器也会自动为您加上(因此也一般不会有程序员这么做)。 而如果加上关键字 则表明该关键创建的变量只会出现非负值的数据。 1.3.数据类型的存储范围 那么每一个类型对应的具体的存储空间大小是多少呢? 补充:如果您是编程新手,可能还需要先了解下面几个视频的知识。 了解什么是二进制,为什么我们需要二进制:关于二进制,没有比这个讲的更清楚的了 这个视频可以了解一些计算机存储单位:Bit、Byte、kb、KB、MB,KiB、MiB 都有什么区别?硬盘容量不符竟是这个的原因! 首先,一旦需要表示存储空间的范围,就必然需要对应的单位来进行描述。 在计算机中,能存储一个二进制位设备,就叫这个设备具有 的存储空间,若是有 个这样的设备构成一个大的设备,就会变成 大小的新设备,因此借助 和 等单位可以帮助我们描述存储二进制设备的空间大小,常见的单位如下: 1B (Byte 一字节) = 8bit 1KB (Kilobyte 千字节) = 1024B 1MB (Mega byte 兆字节,简称“兆”) = 1024KB 1GB (Giga byte 吉字节,又称“千兆”) = 1024MB 1TB (Tera byte 万亿字节,太字节) = 1024GB 1PB (Peta byte 千万亿字节,拍字节) = 1024TB 1EB (Exa byte 百亿亿字节,艾字节) = 1024PB 1ZB (Zetta byte 十万亿亿字节,泽字节) = 1024EB 1YB (Yotta byte 一亿亿亿字节,尧字节) = 1024ZB 1BB (Bronto byte 一千亿亿亿字节) = 1024YB 1NB (Nona byte) = 1024BB 1DB (Dogga byte) = 1024NB 再回到数据类型上,不同的数据类型创建出来的容器/变量的大小是不一样的,能存储数据的范围是也有限的。以下为 平台下的数据类型的存储范围(您大概看一下就行,不同平台的相同关键字可表示的范围也有可能不一样): 数据类型 占内存字节数 能表示的十进制数的范围 char (signed char) 1 [ − 128 , 127 ] [-128,127] [−128,127] unsigned char 1 [ 0 , 255 ] [0,255] [0,255] short int (signed short int) 2 [ − 32 768 , 32 767 ] [-32,768, 32,767] [−32768,32767] unsigned short int 2 [ 0 , 65 535 ] [0, 65,535] [0,65535] int (signed int) 4 [ − 2 147 483 648 , 2 147 483 647 ] 或 [ − 2 31 , 2 31 − 1 ] [-2,147,483,648, 2,147,483,647]或[-2^{31}, 2^{31}-1] [−,]或[−231,231−1] unsigned int 4 [ 0 , 4 294 967 295 ] [0,4,294,967,295] [0,] long int (signed long int) 4 [ − 2 147 483 648 , 2 147 483 647 ] 或 [ − 2 31 , 2 31 − 1 ) [-2,147,483,648, 2,147,483,647]或[-2^{31}, 2^{31}-1) [−,]或[−231,231−1) unsigned long int 4 [ 0 , 4 294 967 295 ] [0, 4,294,967,295] [0,] float 4 [ − 3.4 × 1 0 − 38 , 3.4 × 1 0 38 ] [-3.4 × 10^{-38}, 3.4 × 10^{38}] [−3.4×10−38,3.4×1038] double 8 [ − 1.7 × 1 0 − 308 , 1.7 × 1 0 308 ] [-1.7 × 10^{-308}, 1.7 × 10^{308}] [−1.7×10−308,1.7×10308] long double 8 [ − 1.7 × 1 0 − 308 , 1.7 × 1 0 308 ] [-1.7 × 10^{-308}, 1.7 × 10^{308}] [−1.7×10−308,1.7×10308] 对于一个程序员来说,通常不需要知道精确的表示范围,只需要知道所占字节个数就够了。 不过值得注意的是,使用数据类型创建变量后,若让变量存储一个超过该数据类型的可表示范围时,就有可能导致奇怪的错误(这种也被称为“溢出”)。 注意: 语言标准规定 存储的字节个数只需要 既可以,但是保证 一定大于 。如果您实在好奇您当前的编译环境中,数据类型的存储大小,可以使用关键字 ,它可以用来数据类型关键字的存储大小(以字节为单位)。 2.变量常量 2.1.变量 2.1.1.变量的创建 变量的创建很简单,只需要根据不同的数据的特点,择取对应的关键字以及选择一个名词作为变量名,直接创建即可。 上述的 、、、 是四个变量的变量名,使用它们就是在使用变量。 并且我们还可以注意到,在代码的结尾 变量发生了变动,这也就是为什么变量称为“变量”的原因:在代码运行过程种,可被修改的量。 这里需要强调的概念是定义和声明, 对两个概念分得很开,在使用数据类型关键字时创建变量时,就是“定义了个变量”,而一个变量被定义出来后,再重新赋予新的值时。就是“给变量做赋值”。 另外,在定义变量的同时给与一个量作为变量的初始值时,就是“变量的初始化”。 补充:变量的命名规则 最好随意取变量名,我们需要遵循以下规则: 只能由字母(包括大写和小写)、数字和下划线组成 不能使用数字作为命名的开头 不能使用已有的关键字(您不能给变量取名为 、 等关键字) 最好长度不超过 个字符 最好区分大小写 最好取有意义的英文名词、动词等,非必要不直接使用一个字母作为变量名(过了本节后我的所有代码会严格按照这条…) 最好遵循现有的知名命名规则,例如驼峰命名法、蛇形命名法(这两种我都用)… 2.1.2.变量的作用域 ”作用域“是程序设计的概念,通常来说,代码中所用到的变量名并不是总是有效、可用的,而变量名的可用范围就是该变量的作用域。 局部变量的作用域:在一对 内创建的变量,就是局部变量,可以被使用的范围不能超过 外 全局变量的作用域:在所有 外内创建的变量,在一个工程/源文件中,均可被使用(缺点是可能不够安全,这个被声明的变量容易改变值,引起其他地方的 ,因此大部分程序员会避免使用全局变量) 当全局变量和局部变量的标识符冲突时,优先使用局部变量。 补充:需要注意的是,创建全局变量后, 默认内部初始的值为 ,但是最好由我们自己初始化,时刻对刚创建的变量进行初始化是一种好的编程习惯。 2.1.3.变量的生命周期 ”生命周期“就是指创建变量和销毁变量的时间段。 局部变量的生命周期:局部变量使用完就会被销毁,也就是说局部变量的生命周期从进入作用域时,生命开始,离开作用域时,生命结束。开始和结束之间,就是该局部变量的生命周期。 全局变量的生命周期:全局变量的生命周期和“整个程序”是一样的,程序结束时,全局变量也就被销毁,这种变量的运行时间和 主函数的运行时间差不多。 2.2.常量 注意:有关于常见的四种常量,您只需要知道有哪一些是常量即可,无需在意过多细节。 很多情况下,都可以使用常量来给变量赋值和定义。 2.2.1.字面常量 就是在创建变量中,直接从字面上就可以看出属于什么数据类型的量,例如 。 这里再次注意,数据类型关键字可以起到两个作用,一是创建变量,而是为变量或常量进行分离 注意: 编译器会默认小数类型为 ,所以一定要强调单精度类型的话,就要在小数后面加上 ,比如 ,也就是 。 2.2.2.常变量 创建变量的过程中,使用 关键字可以让这个变量具有常量属性,在后续使用中虽然可以使用改变了,但是无法修改变量的值。 注意:但您需要记住,哪怕被 修饰了也依旧是一个变量,只不过无法被修改罢了(相当于加了把锁)。 2.2.3.宏常量 使用宏语法 可以定义一个常量,这是 的一种特殊语法,我们以后会展开来讲。 2.2.4.枚举常量 枚举常量实际上属于结构体的知识,需要使用关键字 来创建一个枚举体,我们以后会在提及结构体的时候再展开来细讲。 3.字符基础 3.1.字符 语言表示字符由 引起,例如:。 3.2.字符串 字符串由 引起,例如:, 会自动在末尾加上 , 表示“结束”,是一个转义字符,并不算入字符串的内容,只是做一个结束标志。 注意: 和 是有区别的,前者有 结尾,后者则没有! 字符串可以用两种方式存储,这两种方式在存储方式上有些许不同: 您不必纠结什么是指针、数组,您目前只需要知道这样做就可以找到或者存储一个字符串即可。 3.3.转义字符 利用 字符,可以转变某些字符原有的意义,达到其他的目的(用的还是比较多的),常见的有: (打印 )、(打印 )、(打印 )、(打印 ) (换行)、(回车) (水平制表符)、(垂直制表符) (退格符,相当于 ) (换页) ( 表示八进制)、( 表示十六进制)。 上述转移字符可以使用 来验证效果,例如: 就会在终端窗口中进行换行。 补充:这里我给出一份 官方的 转义字符文档,您可以前去查阅一二。 补充:早期的编译器有“三字母词/三联符”概念,例如: 代表 ,因此 是用来转义 的,使用 可以正常打印出 ,而不会被编译器识别为三字母词(类似想打印 ,不能直接写 ,而是要写 ),需要注意的是,不是所有编译器都支持三字母词。 在 中是无法直接得到上述结果的,但如果在 编译器中增加选项 ,就可以看到这个现象(这里了解一下就行)。 3.4.字符编码 实际上,字符需要转化为数字然后存储到计算机中,计算机需要显示字符时,再根据这个数字在字符集中查找对应的字符,然后才显示出来,而字符和数字之间对应的方案就叫做“”。
补充:上述表格来源于 官网文档 ASCII 码表,并且我标注出了最常用的字符,简单记忆一下对应的编码即可。更多关于字符编码的知识可以在视频 锟斤拷�⊠是怎样炼成的——中文显示“⼊”门指南 中深入了解。 表虽然不用全部记忆(用时查即可),但是还是有必要简单记忆一下排布顺序: 字符 的 码值从 字符 的 码值从 对应的⼤小写字符( 和 )的 码值的差值是 (不是 ) 数字字符 的 码值从 在这些字符中 码值从 这 个字符是不可打印字符,⽆法打印在屏幕上观察,他们具有别的特殊用法(都是一些控制字符或者反义字符) 如果输入对应的 码值到变量 中,使用 就打印出对应的字符。另外,还可以利用 来使用 码值表,不过这种是使用八进制和十六进制的 码值来打印字符: 代表一个八进制数字 代表一个十六进制数字 补充:需要注意的是,使用反义字符来表示字符时,其数值不可以超过 ,也就是不能超过 编码范围。 4.代码注释 4.1.注释作用 注释可以让别人看懂自己的代码,或者给未来的自己看懂自己曾经写的代码。再本系列中,我写的代码注释主要让您更深入理解一些代码细节。 4.2.注释方式 语言主要有两种注释方式,一是行注释 ,二是块注释 ,需要注意的是,在双引号引起的字符串内无视这两种注释方式,例如:,这是被允许的,不会被编译器识别出有注释的文本。 补充 : 不可以嵌套使用,例如:,第一个 只会和遇到的第一个出现的 佩对,然后将两者中间的代码屏蔽掉。 补充 : 注释快捷键 用光标选中需要注释的代码,通过快捷键 添加注释, 取消注释,当然,您也可以找找工具栏的按钮,那里也有注释和取消注释的按钮。 补充 :实际上,行注释是 的注释风格,在后面被添加到 中,我更推荐使用这种注释,不会产生配对问题,进而造成奇怪的错误。 补充 :注释会被编译器忽略,在代码被运行后,注释会被替换成空格。 5.逻辑语句 首先我们需要知道,在 语言中,一个表达式的结果不为 ,这代表这个表达式为“真”,如是非零(包括负数)则为“假”。 而根据由表达式做出某些行为,就是控制语句的功能,该表达式可以被称为“条件”。这里我只是简单介绍一些常见的逻辑语句,让您知道如何使用逻辑语句。 5.1.选择语句 5.1.if 语句 语句可以根据某个条件做出选择,基本的代码格式是: 只要 后的条件的结果为真,那么执行代码 ,而不执行代码 。若为假,则跳过代码 ,执行代码 。也就是说,代码 和代码 的结果只会执行一个。 还可以写出一些多分支的循环语句,可以进行多种选择。 如果您愿意,还可以写出具有 种,乃至 种等等多种情况的 语句。 注意:最好不要在 语句后面加上分号,否则输出会出乎您的预料。 上面代码的含义就发生的变化,正确的代码缩进就变成了下面这样。 和上述问题代码等价的代码如下: 因此代码还是会输出 。 5.1.2.switch 语句 语句也是一种选择语句,其作用是根据条件跳转到对应的标签,并且开始执行标签后的代码。 语句会根据入口处的整型表达式的结果来选择对应的标签,将程序跳转到该标签,然后开始执行往下执行代码,其中 语句是必须的,否则代码会一直往后执行,将所有代码执行完才结束 语句。 其中 是可选的,只有在其他标签都没有被匹配到的情况下才会执行。 注意:不要在 后加分号! 5.2.循环语句 循环语句实际上就是 语句的拓展,只不过多加了循环执行的特性。最经典的循环语句就是 ,除此以外, 还有一些其他书写循环的写法。 5.2.1.while 的书写格式是: 因此我们可以借助循环语句,减少一些重复性的工作。 还可以写出一些可以被死循环的代码。 注意:不要轻易在 后加分号,但是有些时候我们确实需要这样做,我们遇到再提及。 5.2.2.do-while 也是书写循环的一种方式,但是和 略有不同,两者在“先判断条件再执行代码,还是先执行代码再判断条件”上的顺序不一样,有时我们的确需要这种语句,简化我们的代码。 在之前的 循环中,我们是写了一个“死循环”(不断执行某些语句的代码,除非手动停下,否则不会自动停止),但是更多的时候,我们会设计出让循环自动停下来的代码,在上面代码中体现在 这句代码中,只要让 不断自增 ,总会出现 的情况,因此就会导致 出的条件为假,导致循环停止。 注意:很多程序员在使用 的时候,常常会忘记在结尾使用 结尾。 5.2.3.for 循环对比其他的循环语句会稍显复杂,我们需要先了解 循环语句的书写格式: 为什么是这种怪异的格式呢?我们回顾一下之前写的一段 代码: 我们可以发现,很多的循环语句都在做上面的事情: 初始化计数器 为 判断计数器是否在某个范围内 执行某些需要被循环的语句 计数器更新(自增 ) 但是这样写出的代码不够简洁,有的时候甚至会漏写某个步骤,这个时候怎么办呢? 提供了一种 循环语句,在循环的入口处就可以初始化计数器,然后根据循环条件判断是否执行循环体内的代码,最后更新计数器。也就是说, 代码的执行流程为 1 → 2 → 3 → 4 → 1 → . . . 1 o 2 o 3 o 4 o 1 o … 1→2→3→4→1→… 补充:另外我们还在循环的流程图中提及了关键字 、,这两个关键字很简单,一个是跳出循环,一个是跳出本次循环。 5.3.goto 语句 提供了 的跳转语句,可以跳转到对应标签处的语句,对于 新手来说尽量少用,容易导致逻辑混乱。 语句在循环多次嵌套的时候非常好用,如果使用 就会写出下面代码: 可以看到必须使用多次 语句才可以跳出最外层的循环,而改用 就只需一次: 6.函数 在 语言中,函数就是处理数据的一份代码块,本质是把需要被重复使用的代码块提炼出来,使用一次函数就代表使用了等价的一份代码块。 5.1.自定义函数 5.1.1.函数结构 一个函数应该具有函数返回值、函数名、函数参数构成的函数签名特征。 在函数的调用中,形式参数拷贝实际参数的传递过来的变量或常量,形成一个新的局部变量(只能在函数内部被使用),因此形参只是实参的临时拷贝,内部数据的存储位置不在同一个位置。 补充:由于形参是实参的临时拷贝,因此在函数中是没有办法直接通过修改形参的值来修改实参的(但是有其他的办法,例如使用指针,这点我们后面提及)。 如果函数的返回值和返回类型不一致,会发生隐式转化,使得返回值和返回类型一致 如果函数具有有返回值,并且函数定义内存在 语句,则必须保证每一个分支都有返回值(在 下会提出警告 ,这说明我们对代码的各种情况考虑不够周全,存在程序无法确定返回值的情况)。 5.1.2.函数分类 根据函数的实现者,可以分为: 库函数:已经写好内部代码的函数,直接使用即可,例如 里的 自定义函数:自己写的一个自定义的函数,例如我们之前自定义的 根据函数是否有返回值(有无 语句),可以分为: 无需返回的函数(无返回值的函数) 需要返回的函数(有返回值的函数) 5.1.3.函数作用 之所以会有函数的存在,其中的几个目的是为了减少代码量、更加清晰地识别代码。 在我们要做某些事情的时候,只需要调用/使用曾经设计好的函数就行,而不必每次都去重复写这个函数里面的内容(也叫做代码的复用)。 类比:就好像想要吃薯片,虽然您可以自己做薯片,但是也可以选择直接买商店的包装薯片,而不必每次都自己去做。而函数就可以类比那些帮你包装好薯片的一个加工厂,加工厂将原料加工为薯片,函数将输入数据加工为返回值,都可以为用户节约时间和成本。 5.2.一些库函数 5.2.1.scanf() 该函数可以根据用户在控制台的输入和格式化字符,给变量设定值,是一个输入函数。 读取失败就返回 ,通常 的值是 正常读取则返回读取到格式化数据的个数 另外,在 ,直接使用 会报一个错误和一个警告: 错误:,出现这个错误,就需要在一开头使用 来屏蔽这个错误(出现这个错误的原因就是因为微软认为 函数不够安全,要求您使用 ,但是为了学到“原汁原味”的 语言,我们最好还是继续使用 )。 但是每一次都需要添加这句话,未免有些麻烦,因此我们可以修改 的文件 ,在内部第一行写入 保存即可,以后创建 文件后就会自动在第一行填充这句语句。 那为什么微软会认为 不安全呢?这和指针有关,我们之后学习了指针再来提及。 警告:“ 的返回值被忽略”,提示我们要用上 的返回值,我们可以暂时忽略这个,编译器只是提醒你:要注意使用返回值,不要轻易忽略。您可以用一个变量 来接收使用 的返回值,也可以不用理会。 有些时候,还可以利用 的返回值来循环输入/多组输入,我们以后有机会再提及。 补充:关于 的符号,待补充… 5.2.2.printf() 该函数可以根据变量的值和格式化字符,输出变量的值到控制台上,是一个输出函数。 的返回值是是输出的字符数量,包括数字,字母,标点符号,空格,转义字符… 我们还可以用这个函数结合操作符 来打印出前面我们提及的“数据类型”的大小,其中注意: 不是一个函数,其作用只是一个数据类型或者变量的大小,可以使用 和 打印出来。 补充:关于 的符号,待补充… 5.2.3.strlen() 可以计算 风格字符串的长度,这里 风格字符串指的就是:以 为结尾的字符串,该函数可以计算出字符串的长度(不包含 的所有字符的个数)。 补充:在之后的学习中,我们还会遇到更多的库函数。这里我给出了一份方便 检索库函数的在线网站,虽然是全英文的,但还是请您留存一份,总有一天您会用上的。 5.2.4.rand() 和 srand() 和 time() 在很多情况下我们都需要随机数,我们可以使用 函数生成伪随机数(真正的随机是无法预测的,我们的计算机只能生成伪随机数)。而生成伪随机数主要依靠下述三个接口: 通过算法计算,来返回一个伪随机值(伪随机值的范围在 之间)。但是每次运行就会发现随机值是一模一样的,这是为什么呢? 这是因为 是基于一个“种子”的值(这个值默认为 )和算法来生成一个伪随机数的,而我们可以依靠 来设置这个种子,从而改变每次运行得到的随机数都是不同的。 但是种子怎么设置呢?用一个随机数?这不就矛盾了么,我们本来就需要得到随机数,怎么可以先用随机数呢,因此我们可以让种子和时间关联起来,使用库函数 。 会返回从 年 月 日 时 分 秒到现在程序运行时间之间的差值,单位为秒(这个数字也叫“时间戳”)。 返回的类型是 类型的,这个 类型本质上其实就是 位或者 位的整型类型( 有很多类型都是由其他类型包装过来的)。 补充: 的参数 如果是非 的指针的话,函数也会将这个返回的差值放在 指向的内存中带回去(也就是输出型参数),这里涉及到指针的知识,等您学完指针再跳回这里查看。 如果 是 ,就只返回这个时间的差值。 因此我们就可以利用这个时刻变化的时间戳作为时刻变化的数值,传入 设置为随机种子,让 每次运行都生成不一样的为随机数。 7.数组 7.1.一维数组 7.1.1.数组的创建初始 有时候我们需要大量相同数据类型的变量,这个时候就可以使用数组同时存放一组相同类型的变量。
补充:数组实际上是一种数据结构,关于数据结构的知识,我们以后再提及,这里只需要简单使用即可… 数组的存在可以让您不用一个一个的定义多个变量,减少重复的工作量。但是需要注意的是,数组的大小必须是常量,不能是变量。 7.1.2.数组的下标访问 那如何取出数组里单个的变量/素呢? 使用“数组的下标/索引”来数组内的素,下标从 开始,可以用下标访问一个素。 需要注意的是, 的下标必须是大于等于 ,小于数组素个数的整数值。也就是必须遵循 处于 [ 0 , 数组素个数 ) [0, 数组素个数) [0,数组素个数) 这个区间中,否则有可能出现“下标越界”造成程序奔溃的结果。 疑问:为什么数组的下标不从 开始而从 开始呢?这点您简单查询一下即可,这种设定实际上也是编码实践的结果,或者直接当作约定吧… 7.1.3.数组的存储方式 数组在内存中是怎么存储的呢?实际上,创建数组时,编译器向内存申请了一块连续的内存空间,然后划分空间,每一块空间的大小和指定的数据类型相同,然后允许用户使用下标对这些划分好的空间进行使用(存数据或取数据)。 7.1.4.数组的素个数 数组的素个数多少由您做决定,但是有些时候,一个数组的素个数经常会被您改变,如果在代码的其他地方也需要数组的素个数,那么您改动数组定义处的个数时,使用到数组素个数的相关代码中也需要做同步做出改动。 这样万一有很多处代码都需要使用数组的素个数呢?用变量给定数组的素个数?这是不行的,指定数组内素个数的量必须是常量,而不能是变量。 我们可以结合 关键字,实时计算出数组的素个数供给他人使用。 这样就方便了许多,只需要修改一处就可以。 吐槽:我一直感觉非常奇怪的一件事情,就是大家习惯上会称呼 语言数组的素个数为“数组大小”,但我个人认为这么有有些奇怪,假设你问一个 程序员 的大小是多少,他几乎会说是 字节,不考虑平台的情况下,这没什么问题。 但是你问一个 程序员, 的大小是多少呀?咦?我应该回答 字节,还是以存储 个 素? 个人认为还是将数组的大小归为所占字节大小比较合适,而另外一个直接使用数组的“素个数”,而不应该混杂使用,造成误解(当然,这只是个人见解,实际上不改变也没什么大的问题,您心里清楚就可以)。 7.2.高维数组 在 语言中,数组可以存储所有类型,甚至存储数组类型,因此就会诞生二维、三维、乃至更多维的数组(类似“套娃”)。 首先我们来看二维数组,二维数组实际上也是一个一维数组,只不过这次这个一维数组存储的不再是简单的数据类型 、 等,而是一个数组类型。 的含义可以理解为 ,也就是一个有 个素的一维数组,但是每一个素的类型是 ,其本身也是一个数组。 因此二维数组的初始化可以这么写: 对于数组 来说,可以使用下标来访问每一个素,也就是每一个子数组,例如: 对应第二个数组。 但是对于子数组来说,数组 的下标访问就是自己的数组名,例如: 实际上就是第三个数组的数组名,数组名加下标可以访问子数组的素, 就是素 。 根据这个逻辑,我们可以写双循环来遍历整个二维数组: 二维数组的初始化和一维数组的初始化有一些不太一样的地方。在声明二维数组并且定义的时候,不能省略列,只能省略行。原因也很简单: 如果省略列,编译器哪怕知道有多少行,也无法确定一行到底有多少个素 如果省略行,编译器只要知道有多少列,也可以确定一行最多有多少个素 因此读到这里,您完全可以使用二维数组存储一位数组的思维,创造出更加复杂的多维数组,而它们的访问方法也是相类似的。而所有多维数组的存储方式,和一维数组都是采取了顺序存储。 7.3.变长数组 我们之前说数组的素个数只能用常量指定,但是 标准里的”变长数组“却支持使用变量指定(注意不是长度可变就叫”变长“,只是单纯可以使用变量指定而已,不要被这个名字迷惑了)。 但是这一特性有些编译器不支持,比如:,而实际上使用这一新规则的人也不算多,您了解一下即可,或者读一下《C primer plus》中对于 的变长数组()的探讨,那里写得很详细。 不过 中的 标准貌似并不支持变长数组,有机会我带您去其他编译平台用一用(例如 )。 8.操作符 语言很灵活,操作符/运算符有很多,操作符需要多少个量进行运算,就说该操作符有多少个操作数,例如: 操作符需要两个操作数才可以进行加法运算,因此也叫三目运算符。 由于我们第一次接触操作符,因此我们来细细讲解一些操作符的细节。 8.1.算术操作符 :对两个操作数做加法 :对两个操作数做减法 :对两个操作数做乘法,但乘法没有办法像数学一样缩写, 不能写成 ,也不能写成 :对两个操作数做除法 :对两个操作数做取模,且操作数必须都是整数,该操作符可以得到两个操作数相除。如果是负数,结果以第一个操作数的符号 8.2.移位操作符 :对一个整数的二进制做逻辑左移,末尾直接补零即可 :对一个整数的二进制做逻辑右移,开头一般补符号位 :按位与 :按位异或 :按位或 :对一个操作数的存储结果二进制序列全部位取反 8.3.赋值操作符 :这个我们之前就用过,不过这里强调一点,”初始化()和赋值()是两个概念“ 、、、:先做 操作,再赋值 、、:先做 操作,再赋值 、:先做 操作,再赋值 8.4.符号操作符 、,这两个操作符不是加法和减法,而是正负数的前缀符号,用来标明操作数的正负性。 8.5.关系操作符 、、、,这些操作符和数学上的定义、使用几乎一模一样,需要注意的是等号和不等号的写法( 和 )。 补充 :将“ 等于符号”写成“ 赋值符号”的例子比比皆是,还请您注意。 补充 :语法规定上,数学上的区间 必须被写成 或 ,而不能写成 ,这样写代码不会报错,但是逻辑上是错误的。 8.6.逻辑操作符 首先我们需要知道 语言对真和假的理解:视一切非零值为真(包括负数)。 :对于 中,只有两个子表达式同时为真,整个表达式才为真,否则为假 :对于子 中,只要两个子表达式中有一个为真,整个表达式就为真,否则为假 :对于 ,若子表达式为真,则整个表达式就为假,否则整个表达式为真 另外还需要重点提及的是 和 的短路特性,对于 中,如果左边的子表达式为假,则无需执行左边的子表达式(因为根据第一个子表达式,已经足够判定整个表达式是假了)。同样。对于 也是如此,如果左表达式为真,则无需执行右边的子表达式(因为已经可以肯定整个表达式必为真了) 。 8.7.条件操作符 对于 : 若是 为真,整个表达式的结果为 若是 为假,整个表达式的结果为 咦?这个操作符可以做到和 语句类似的效果,好像哪一个都可以呀?一般条件操作符只能写一些简单的条件判断,对于多个条件的判断(尤其是带有嵌套的 语句),使用 语句可读性更好,代码书写的顾虑较少。 上面的条件判断就可以改写为下面的条件操作符,可以看到代码确实简洁了许多。 8.8.逗号操作符 对于 从左往右算,则最后一个表达式 的值为整个式子的值。 8.9.地址操作符 可以对一个变量进行取地址,我们下面讲解指针的时候会使用这个操作符,这里先跳过,简单了解一下就行。 8.10.结构操作符 下标引用 、使用结构成员的 和 ,这两个操作符需要有关于结构体的知识您才能理解。 8.11.自增操作符 、,分别可以对变量进行加一和减一,且具有副作用(这里的副作用就是指导致变量的值也会发生改变)。 另外,前置和后置有很大的区别,简单来说后置的先使用变量,再语句结束时发生副作用。而前置的时候,先发生副作用再使用变量。 补充:有种说法是认为前置 要比后置 要高效,实际上这在 语言的 操作符和 的内置类型 操作符中体现不出来(现代硬件的计算速度还是很快的),但是在 的自定义类型中重载后 的的确存在这种区别,不过这就涉及到 语言的使用了,我们就不提及,只是做简单的解释。 8.12.其他操作符 应该是属于运算符,而不是函数,其本身不会真的进行某些计算(有机会我们验证一下) 这个括号操作符和某个函数名结合使用,变成函数调用或者标识一个关键字是函数,但是实际上大家常常忽略这个操作符,知道和不知道没什么太大区别,在函数调用 中, 的操作对象是 、、 的主要的作用就是利用数组的下标来访问数组的各个素 和 是属于结构体知识的,后期会讲 8.13.优先级和结合方向 当不同的运算符共用同一个变量时,多个运算符就需要规定哪个运算符先进行计算的问题。 如果是不同运算符共用一个变量时,变量就会根据运算符的优先级来决定先被哪一个运算符进行计算 如果同一个运算符共用一个变量时,就会根据结合方向决定先使用操作数左边的运算符还是右边的运算符。
9.关键字 我们之前提到的数据类型关键字,是 语⾔中关键字的一类。而之前提到的条件、循环,也需要关键字 、、、 来构成逻辑语句。 实际上,大多数的编程语言都会有一批保留名字的符号,这些名字不能交给用户去定义变量名和函数名,具有特殊的含义和语法使用规则。这些名字就被被称为“关键字”,或者说“保留字”、“关键词”。在 中,常见的关键字有:。 这些关键字您可以自己稍微拓展学习一下,在以后的学习中我还会提及,您也需要熟记这些关键字(实际上敲多了就记住了,本人英语很烂,也是靠不断的敲击记忆的…)。 补充:这里我也给您留了一份 关键字的相关文档,您也可以前去大概一览,不用特别勉强。 下面我简单介绍几个比较容易理解的关键字,让您能快速理解并且使用更多的关键字。 9.1.typedef 假设有个人的名字全称是“欧阳李四”,而他村里的人却叫他小名“狗蛋”。这就相当于起了一个“别名”,故 的作用就像是给一些类型起了个“别名”。(叫“狗蛋”是为了方便,而您使用 也是为了方便) 在很多地方都会使用 来对某些类型进行重定义,主要是为了对一些很长的类型进行简化使用,例如 可以使用 简化为 。 9.2.extern 我们之前有提到全局变量的概念,但是我们理解的有些狭隘,全局变量不仅仅是在单独的一个 文件中全部可见,甚至在多个 文件中都是可见的,只需要使用 即可。 可以跨 文件声明一个被 修饰的变量,该变量在别处文件中被定义。并且函数也可以使用这个关键字。 吐槽:不过我们一般很少使用这个关键字,以后还会继续谈一谈这个关键字。 补充:在 语言中,对于函数来说,默认为 ,不需要在声明时额外加上 关键字。也就是说,函数的声明默认就是外部可见的,这意味着如果一个函数在一个源文件中被定义,那么它可以被其他源文件中的代码所调用,无需在每个调用该函数的源文件中都显式地使用 extern 关键字声明。这是因为编译器在编译过程中会自动处理函数的外部链接。 然而,对于变量来说情况就不同了。全局变量如果在多个源文件中使用,则需要在除了定义该变量的文件外的其他文件中使用 关键字来声明,以确保所有源文件中的该变量引用的是同一个内存地址。 但是,上述只是我印象中的语法,虽然我也在 中检验过了,但是没有在其他编译器中尝试过。因此我的建议是,如果一定要把某个源文件内定义的函数分享给所有源文件的话,最好还是加上 关键字。 9.3.static 9.3.1.用在局部变量上 我们一直在说变量是存储在内存中的,但是究竟存储到内存中的那里呢?从 语言的角度来说,内存空间可以暂时被我们简单划分为: 栈区:存储局部变量、函数的形式参数 堆区:存储动态内存分配(像 、、、 等函数就是在这块区域做操作的,这个区域的变量我们以后学习) 静态区:存储静态变量、全局变量(静态区里的东西,只有整个程序结束才会被销毁) 而关键字 的翻译是“静态”,它可以做到将一个原本在栈区存储的局部迁移到静态区存储,该局部变量就拥有了和全局变量一样的生命周期(但是作用域依旧是局部的,也就是说,局部变量的作用域依旧不变,出了定义局部变量的代码块时,无法被直接使用,但是该变量内部的值依旧还存在没有被销毁)。 类比:上述使用 的场景可以类比如下。 加了 就相当于:你在做数学试卷,被老师叫去谈话,当你回来的时候,继续往下做题,没有必要重新从第一题开始。 不加 就相当于:你在做数学试卷,被老师叫去谈话,当你回来的时候,又从头开始做题了。 9.3.2.用在全局变量上 关键字还可以使得全局变量只能在本源文件里面使用,这样的话就有对这个变量进行隐藏的功能(不会被别人看到并且使用,只能在本文件中使用,有保密的效果)。 与 的同时出现时,会让 的作用会消失。 9.3.3.用在函数上 用在函数上和用在全局变量上类似,也会让 的作用消失,让函数成为只有定义函数的文件内部才可以使用。 10.宏 关于宏的使用,我们在前面有简单提及过,我们这里来详细讲解一下宏的两种主要用法。 10.1.宏常量 宏语法实际上时一种文本替换语法,如果在代码中定义的宏,则对应使用宏的地方,会在整个代码编译的时候替换为对应的宏值。 10.2.宏体/宏函数 宏体实际上也和宏常量差不多,只不过被替换进去的不再是单纯的宏值,而是一个宏体,其用法很像一个函数。 当然,宏与函数是有较大区别的,不过先不着急了解,慢慢体会。 11.指针 在学习指针之前,首先要理解一点关于内存的基本概念概念。 11.1.内存是什么 内存是电脑上特别重要的存储器,计算机中的程序都是在内存中运行的,内存在哪里呢?就在内存条中,这也就是为什么有些人会追求 、 甚至更大内存的电脑,内存越大,同时运行的程序也就会越多,程序可以使用的资源空间而就越多,电脑整体来看也就越快。 而内存条内的内存空间,可以被分割成一个个“小内存”,这个“小内存”可以用一个小格子表示,每个小格子有一个对应的编号,计算机就可以依靠这些编号来快速找到这个小格子。 而这个编号就是内存地址,这个小格子就叫内存单,内存单一般可以存放一个字节的数据(刚好放一个 类型的数据,大小是 比特,或者叫”八位组“)。 注意:这里必须要区分开内存和磁盘的区别 内存是计算机的工作区,记录计算机当前所需要的数据,内存读取/写入速度很快,但空间小,价格较贵 磁盘是计算机的存储区,保存计算机未来可能需要的数据,磁盘读取/写入速度很慢,但空间大,价格便宜 因此,使用数据类型创建变量的过程,实际上就是计算机把内存中部分的内存空间的所有权分配给变量来进行读写操作,对于计算机可以通过地址来寻找这块已经被分配的内存空间,而对于程序员可以通过变量名字,也就是标识符来寻找这块已经被分配的内存空间。 11.2.编址如何编 位电脑实际就是指有 根地址线(相当于电线),电线是可以被通电的,有高电平,低电平,转化为数字信号就是 或者 。 个二进制位就可以产生 2 32 2^{32} 232 个编号,也就能够产生 2 32 2^{32} 232 个地址。管理 2 32 2^{32} 232 个内存单,而一个内存单占据一个字节,因此也就是管理了 大小的空间。 因此,对于一般的 位电脑,使用 大小的内存就足够使用了。 注意: 位的电脑也可以类似理解,可以使用 的内存条。 11.3.指针怎么用 此时我们就可以更深刻理解 的含义:向内存申请 个字节的空间,存储 这个值。
实际上占用了 个字节的空间,但每个字节都有地址,我们通常取第一个字节的地址(也就是编址最小的地址)作为变量的地址。而变量的地址在 语言中可以使用 来,对于上例中的 变量,其地址可以用 来。 地址实际上也是数据,理应也可以被存储起来,而存储就需要借助变量这个容器,而创建变量就需要使用对于的类型。我相信您应该知道,整数类型使用整形 ,浮点类型使用浮点型 ,那么地址数据呢?使用的就是指针类型。 在 中,通过取地址操作符 把 的地址存放到变量 中, 就是一个存放地址的变量,它的类型是 ,这是一种指针类型。而我们把 叫做一个”指针变量“,也经常被简称为”指针“。 指针类型是派生类型,因此您可以按照以下方式创建出其他指针类型的指针变量: 而既然有变量名字作为标识符,为什么还需要用到指针呢?有一些特殊场景是必须要使用指针的,这点我后面提及。 另外,您可以使用代码打印出地址来验证一下,查看数组的每一个素是不是按照顺序进行存储的,检验我们之前对数组内空间是连续分配/顺序存储的结论。 打印出来的数组素所对应空间的地址是十六进制的,相邻两个地址的差值都是 ,刚好就是一个 的大小,也就代表 个内存单。 提问:当然指针变量本身也是一个变量,那么指针变量本身的大小是多少个字节呢? 而如果已经有了地址,如果访问对应的变量呢?可以使用解引用操作符 。 所访问到的变量,实际上就是变量 。 总结起来的话,使用 可以取得变量对应的地址,使用 可以取得地址对应的变量,两者是互逆操作。 11.4.指针的大小 指针变量也是变量,如果需要存储地址数据,那也是需要空间来存放的,因此对于任意的一个指针类型: 32 位机器上:32 个 0/1 组成的 2 进制序列,需要 32 位 bit 位的空间存储,指针变量就需要 4 个字节的空间来进行存储 64 位机器上:64 个 0/1 组成的 2 进制序列,需要 64 位 bit 位的空间存储,指针变量就需要 8 个字节的空间来进行存储 12.结构体 结构体是为了描述更多的复杂变量而诞生的,有些变量仅仅靠单个的 、、…来描述是不够的,这个时候就需要结构体的存在了,结构体可以让用户创建一个复合的类型。 是创建结构体的关键字,结构的存在使得描述一个变量时,可以使用多个数据类型来综合描述,内部每一个成员就是结构体成员。 而结构体成员可以利用 或者 来访问,两者用法是有区别的,但是作用类似。 可以看到,直接使用 定义的结构体类型,可以创建出一个对应类型的变量。 13.编码素养 13.1.编码习惯 编写 的代码过程中,有很多的代码风格值得我们学习,我这里只总结了一些常见的代码风格习惯。 不要使用及其简短的变量名字,除非您真的不知道该命名什么(尽管一开始我的确有这么使用,但是之后的文章我几乎不会这么干) 使用合适的代码缩进风格,必要时使用空格和空行分割代码块 适当书写一些注释,避免未来自己都看不懂自己写的代码 多多实际敲敲代码,不要只停留在理论上,否则没有书写代码的感觉 尝试使用 代码管理工具来推送代码,通过记录提交来记录自己的学习路途(这非常有成就感),这类视频有很多,您可以上 站搜寻一番 13.2.编码自救 我们在编写代码中,难免会出现错误,我们首先需要做到以下几点: 不畏惧编译器的错误提示(包括警告) 编译器给出的提示不仅仅是错误提示,还包含警告提示,不要忽略后者 拥有一定的搜索检索能力,不要用 (乐色堆,您不如直接使用 ),推荐使用 ,也提倡使用多个平台的内置收索,小红书也好、 也好、博客园也好、知乎也好,甚至您有办法的话,使用 、 也是可以的,总之,站到更远的互联网,不要拘泥于国内互联网的内容 有些时候,编译器也会“犯迷糊”,您写的代码在编译器看来可能暂时没有错误,但这不代表以后没有错误(有可能含有隐藏的 ,但这不会导致运行出错)。这是新手常会误会的错误,认为只要有好的编译器,并且代码能运行就说明正确。但实际上,写代码时我们要尽可能足够严谨,否则就会在某一时刻出现意想不到的错误 当然以上仅仅个人拙见…
2024最新激活全家桶教程,稳定运行到2099年,请移步至置顶文章:https://sigusoft.com/99576.html
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。 文章由激活谷谷主-小谷整理,转载请注明出处:https://sigusoft.com/49963.html