C语言学习笔记

借助C Primer Plus第六版中文版

[TOC]

一、初识C语言

基本概览,没啥好记的

二、 C语言概述

简单的C程序示例

注释略显杂乱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 这是单行注释
/*
这是多行注释
*/

#include <stdio.h> //预处理器指令,包含一个文件。C程序顶部的信息集合被称为头文件
int main(void) //main总是第一个被调用的函数,void表示main不带任何参数
{ //函数体开始,必须使用花括号
int num; //声明变量,int代表整型。int是关键字,num是变量,属于标识符
//给变量命名:只能大小写字母、数字和下划线,第一个字符不能是数字。尽量避免下划线开头,可能会与其他标识符冲突
num = 1; //赋值表达语句,从右侧把值赋到左侧

printf("I am a simple "); //调用printf函数,printf是函数名,圆括号内是参数。可以理解为这里控制权从main转给了printf
printf("computer.\n"); // \n是一个转移序列,代表换行
printf("My favourite number is %d because it is first.\n", num); // %d是占位符,指明输出num的位置

return 0; //return语句,目前可以看做是结束main函数的要求
} //函数体结束

简单程序的结构

包含函数头和函数体,函数体又主要有声明和语句

建议在一开始就进行声明,虽然新规则没有要求了但最好还是这样

大部分语句都以分号结尾!!!不要忘记!!!

提高程序可读性

  1. 积极写注释
  2. 使用有意义的变量名
  3. 使用空行分隔
  4. 每行一条语句
  5. 程序第一行就写注释,包括程序名称和目的

进一步使用C

同时声明多个变量:使用逗号隔开

1
int a, b;

打印多个值:同样用逗号隔开

1
printf("I like %d and %d !\n", food, drink);

待打印的值不一定是变量

1
printf("The answer is %d !", 3 * total);

多个函数

可以将自己的函数加入到程序中。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void butler(void); //函数原型,告诉编译器在程序中要使用该函数,类似声明
int main(void)
{
printf("I will summon the butler function.\n");
butler(); //调用函数,写出函数名和圆括号即可
printf("Yes. Bring me some tea and writeable DVDs.\n");

return 0;
}
void butler(void) //函数定义,即函数本身的源代码。同时这也是函数头,其信息表明butler不带任何参数,且没有返回值
{
printf("You rang, sir?\n");
}
//函数放的位置和运行顺序没有关系,只看什么时候被调用。一般而言main函数放在第一个,因为它提供了程序的基本框架

调试程序

语法错误:把有效的C符号放在了错误的地方

如:用圆括号代替了花括号;声明变量的写法错误;多行注释没写末尾*/;没打分号等等

语义错误:语法正确,意思上的错误

编译器无法检测,因为语言规则正确,但是运行结果不符合预期要求

如何监视程序状态?

  1. 对于简单代码,假设自己是计算机,模拟运行一遍
  2. 在程序关键点插入额外的printf,了解程序执行情况
  3. 利用调试器(推荐)

关键字和保留标识符

一些关键字比较特殊,不能用它们作为标识符,如int、while、break等等

还有一些是保留标识符,C语言已经制定了他们的用途或者保留了他们的使用权,比如那些以下划线字符开头的标识符和标准库函数名,比如printf

练习

进行了第5题和第8题的练习

copilot真的太好用了你知道吗?不过为了练习需求还是不开了

三、数据和C

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main(void)
{
float weight; //新的浮点数类型的变量。其可以储存带小数的数字
float value;

printf("Are you worth your weight in platinum?\n");
printf("Let's check it out.\n");
printf("Please enter your weight in pounds: ");

scanf("%f", &weight); //用于读取键盘输入,%f表示读取浮点数。&weight表示把输入值赋给weight变量
value = 1700.0 * weight * 14.5833;
printf("Your weight in platinum is worth $%.2f.\n", value); //%.2f表示输出输出浮点数显示小数点的后两位
printf("You are easily worth that! If platinum prices drop,\n");
printf("eat more to maintain your value.\n");

return 0;
}

变量与常量数据

程序运行过程中没有变化的量叫做常量,在程序运行期间可能会被改变或者被赋值的叫做变量

数据:数据类型和关键字

对变量而言,要在声明时指定其类型

  • int表示基本的整数类型,其关键字long、short、unsigned用于提供其变式
  • char表示字母和其他字符,也可以表示较小的整数
  • float、double和long double表示带小数点的数
  • _Bool表示布尔值(true和false)
  • _complex表示复数
  • _Imaginary表示虚数

按照计算机的存储方式可以分为两大基本类型:整数类型和浮点数类型

位、字节和字

  • 位bit:最小存储单元,可以储存0或1
  • 字节byte:1字节有8位,有256种组合
  • 字word:设计计算机时给定的自然存储单位,目前一般为64位。字长越大,数据转移越快,允许的内存访问也就更多

整数

8位字节中储存,二进制。例如7表示为0000111

浮点数

第一位表示正负号,最后一位表示10的几次幂

C语言基本数据类型

int类型

目前计算机一般是64位,但是大多数情况任然用32位储存一个int值

  1. 声明int变量:用多行或者逗号创建变量。此步是为变量创建存储空间

  2. 初始化变量:也就是为变量赋一个初始值。方法如下:

    1. 直接赋值

      1
      cows = 100;
    2. 通过函数比如scanf

    3. 声明时就初始化

      1
      2
      3
      int cats = 100, dogs = 200;
      //不要像下面这样,把未初始化和初始化的放在同一行,容易误解
      int dogs, cats = 100;
  3. int类型常量:C把不含小数点和指数的数作为整数,它们都是整形常量。非常大的整数除外

  4. 打印int值:使用printf打印,%d(转换说明)表明了打印整数的位置(%d与所有int类型相匹配)。要确保转换说明的数量与待打印值的数量相同

  5. 八进制和十六进制:0前缀表示八进制,0x或者0X表示16进制。如16是020,也是0x10

  6. 显示八进制和十六进制:将%d换为%o或者%x、%X,就可以以8进制或者16进制显示数字。如果要在前面加上0和0x前缀,改为%#o或者%#x、%#X

其他整数类型(前缀)

  1. short,占空间比int少,用于较小数值场合
  2. long,占用存储空间比int多,较大数值场合
  3. unsigned,用于非负值场合

如何选择?

  1. 先考虑unsigned类型,此类型一般用于计数
  2. 超出int范围,则使用long,甚至long long
  3. short用于节省空间

通常,编译器会“自动”为数字选择对应的存储类型

若想在int为16为,long为32位的系统中,将“自动”存储为int类型的数值用long类型存储,则可以在该值的末尾加上l或L后缀。一般使用L因为小写l容易弄混。类似的,8进制和16进制也可以;long long则是两个L;u或U表示unsigned

1
2
3
4
//例如:
a = 7L;
b = 10ULL;
//貌似这里识别有问题,不用管

打印short、long等类型

转换说明:

  1. unsigned int:%u

  2. long:%ld(若系统中int和long大小相同那么%d就可以)

    八进制或者十六进制就是在对应字母前加上l,如%lo、%lx

  3. short:%hd,其他进制类似long,但是前缀是h

  4. 组合unsigned:在最后加上u后缀,如%lu、%llu

使用错误的转换说明会导致输出错误的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//例子如下:
#include <stdio.h>
int main(void)
{
unsigned int un = 3000000000;
short end = 200;
long big = 65537;
long long verybig = 12345678908642;

printf("un = %u and not %d\n", un, un);
printf("end = %hd and not %d\n", end, end);
printf("big = %ld and not %hd\n", big, big);
printf("verybig = %lld and not %ld\n", verybig, verybig);

return 0;
}

/*
输出结果如下:
un = 3000000000 and not -1294967296 无符号和有符号,这两个结果在系统内存中表示完全相同
end = 200 and not 200 编译器自动把short值转换为了int,因为这样处理最高效
big = 65537 and not 1 使用%hd时,只会截取最后16位
verybig = 12345678908642 and not 1942899938 类似上一行,%ld截取最后32位
*/

整数溢出
如果整数超过了取值范围就会溢出。unsigned溢出后从0开始,int从-2147483648开始

使用字符:char类型

实际上char存储的是整数,是整数类型。所以使用ASCII编码,用特定整数代表特定字符。也有其他编码方式如Unicode

声明char类型变量

类似其他

1
char response;

字符常量和初始化

注意:C语言中单引号双引号不同!

1
2
3
4
5
6
7
char broiled;
broiled = 'T'; //正确,单引号括起来的叫做字符常量
broiled = T; //错误!此时T是一个变量
broiled = "T"; //错误!此时"T"是一个字符串

//事实上,char是以整数形式存储的,所以以下方式也可以(不妥当)
char grade = 65;

非打印字符

如退格、换行等,可以用3种方式表示这些字符

1
2
3
4
5
6
7
8
// 1.使用ASCII码,如蜂鸣字符的ASCII值是7
char beep = 7;

// 2. 使用转移序列(重要,对照表在后面)
char nerf = '\n'
//代表换行。那么打印nerf的作用就是另起一行

// 3.使用十六进制表示字符常量

转义序列部分对照

  • \a:警报
  • \b:退格,移动光标位置往前一格,不是删除
    ​ 输入时数据会替换掉后面的字符
  • \f:换页
  • \n:换行
  • \r:回车(回到本行第一个字符)
  • \t:水平制表符
  • \v:垂直制表符
  • \\:取消对\的转义
  • \‘:单引号’
  • \“:双引号’’
  • ?:问号?
  • \0:后面接0-7的数字,用八进制值ASCII码表示字符
  • \x:后面接0-f,用十六进制值ASCII码表示字符

一些需要注意的

  1. 使用ASCII码时注意数字和数字字符的区别。如字符4对应的ASCII码是52,’4’表示字符4而不是数值4
  2. 双引号中不需要再把转移序列用单引号括起来
  3. 优先使用转移序列而不是ASCII码

打印字符

在printf用%c表示待打印的字符

如果使用%d则会直接打印一个整数

有符号&无符号

有些编译器把char视为有符号类型,那么此时char表示的范围就是-128~127;有的视为无符号类型,那么范围就是0~255

可以在char前面使用signed或者unsigned

_Bool类型

用于表示布尔值true或者false,1表示true,0表示false

所以_Bool实际上也是一种整数类型

可移植类型:stdint.h和inttypes,h

头文件需要使用<inttypes.h>

此数据类型略

float、double和long double

C标准规定,float至少能表示6位有效数字,double至少可以表示10位有效数字。float一般占用32位,其中8位表示指数的的值和符号,身下24位用于表示非质指数部分;double一般占用64位而不是32位,因此至少可以表示13位有效数字

声明浮点型变量

1
2
3
4
float noah, jonah;
float trouble;
float planck = 6.63e-34;
long double gnp;

浮点型常量

基本形式:有符号的数字(包括小数点),后面紧跟e或E,最后是一个有符号数表示10的指数

可以省略正号;可以省略小数点(2e5)指数部分(19.28);可以省略小数部分(3.e16)整数部分(.45e-6)

e和E左右都不要加空格

默认情况下,编译器假定float是double类型的精度并使用双精度进行乘法运算,然后将结果截断成float,这样计算精度更高但是会损失速度。若要强制视为float类型,在数据最后加上f或F。若要强制视为long double类型,加上l或L。

C99标准添加了一种用16进制表示浮点型常量的方法,此处略

打印浮点值

在printf中使用%f表示待打印的浮点值。若要打印指数计数法的形式,用%e(C99标准也可以用%a)

打印long double,使用%Lf、%Le(或%La)

浮点值的上溢overflow和下溢underflow

上溢:当计算值过大超出当前类型能表达的范围,printf显示为inf或infinity

下溢:因为精度原因损失了有效数位,0.1234e-10除以10得到0.0123e-10

未定义浮点值
如给asin()函数输入一个大于1的值,因为sin的范围不能大于1,所以返回值是NaN,printf会显示为nan等

浮点值舍入错误
若浮点能够储存的有效数字少于运算所需的,就可能发生错误,因为计算机缺少足够的小数位来完成运算。
例如2.0e20先加1,再减2.0e20,得到的结果并不是1

复数和虚数类型

三种复数类型:float_Complex、double_Complex和long double_Complex

类似地,三种虚数类型float_Imaginary,后略

这些类型的变量包括两个float类型的值,分别用于表示复数的实部和虚部

如果包含complex.h头文件,则可使用complex、imaginary用来代替_Complex和_Imaginary

类型大小

使用sizeof()运算符,以字节为单位给出指定类型的大小,在printf中用%zd表示

1
printf("The int has a size of %zd bytes.\n", sizeof(int));

使用数据类型

初始化变量时应使用与变量类型匹配的常数类型

若类型不对应,会损失部分数据,例如float转化为int会直接丢弃小数点后的值,double转化为float会损失精度

参数和陷阱

传递给函数的信息称为参数

printf中参数不对应时并不会给出警告,如果发现程序输出与预期不符,可以进行检查

程序运行情况

刷新输出

printf会将输出发送到一个叫做“缓冲区”的中间存储区域,然后缓冲区的内容再不断被发送到屏幕上

当缓冲区满、遇到换行符或者需要输入的时候,会将缓冲区的内容发送到屏幕上

练习

练习2、4、5、6四道题,目前题目依旧比较简单,基本10行内都可以搞定

简单了解了一下还没学的scanf的用法

四、字符串和格式化输入/输出

字符串简介

字符串是一个或者多个字符的序列

用双引号括起,“告诉程序这是字符串”

char类型数组和null字符串

C语言的字符串储存在char类型的数组中,每个单元储存一个字符

数组末尾的字符 \0 是空字符,C语言用它标记字符串的结束,它不是数字0,是非打印字符

C中的字符串一定以空字符结束,所以数组容量必须至少比带存储字符串中的字符数多1

数组

数组是同类型数据元素的有序序列

1
2
3
4
//通过以下声明创建了一个包含40个存储单元(元素)的数组
//每个单元储存一个char类型的值
char name[40];
//char表示存储元素的类型,[]表明是数组,40表示数组中的元素数量

使用字符串

在printf中使用%s代表待打印的字符串

scanf在读取输入时,会自动把空字符放入字符串的末尾

字符和字符串

字符串常量”x”和字符常量’x’不同。

  1. ‘x’是基本类型char,而”x”是派生类型(char数组)
  2. “x”实际由两个字符组成,”x”和空字符\0

strlen()函数

sizeof()函数以字节为单位给出对象的大小。strlen()函数给出字符串中的字符长度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//sizeof与strlen对比
#include <stdio.h> //stdio.h头文件包含标准函数,如printf和scanf
#include <string.h> //使用strlen则需要使用该头文件。string.h头文件包含一些与字符串有关的函数,如strlen()函数
#define PRAISE "You are an extraordinary being." //C预处理器,后面会讲到
int main(void)
{
char name[40];

printf("What's your name?");
scanf("%s", name);
printf("Hello, %s. %s\n", name, PRAISE);
printf("Your name of %zd letters occupies %zd memory cells.\n", strlen(name), sizeof name);
//此处的sizeof并不需要括号。当运算对象是类型时,必须加括号,如sizeof(int), sizeof(float); 而对于特定量则可有可无,如sizeof 6.28, sizeof name。但是最好所有情况都加括号。
//使用很长的printf,可以在参数之间断为两行(不要在双引号的字符串中间断开),或是使用两次printf,前一次不打\n即可
printf("The phrase of praise has %zd letters ", strlen(PRAISE));
printf("and occupies %zd memory cells.\n", sizeof PRAISE);

return 0;
}

如输入Serendipity Chane,得到输出:sizeof显示name数组有40个存储单元,但只有11个单元用来储存输入的Serendipity,因此strlen()输出结果11

注意:此处输出11并不是因为strlen()计数到了空格,而是因为scanf的%s是读取单词,碰到Serendipity后的空格之后就停止了读取,所以只有Serendipity被输入了name变量,chance没有被输入

而strlen(PRAISE)输出是31,可见空格也算作一个字符。sizeof输出则是32,因为把字符串末尾不可见的空字符也算在内了

常量和C预处理器

有时候需要在程序中使用常量,如使用圆周率3.14159,一般而言,可以直接输入这个值,但是使用一格1符号常量,如π,代替它会更好,原因如下:

  1. 常量名比单纯的数字表达的信息更多
  2. 假设程序多处要使用某一个常量,例如税率,其经常变化,需要在程序中反复多次修改不方便,使用符号变量可以简单做到而无需在程序中查找

我们一般可以使用声明变量并给其赋值的方法,但是可能会导致无意中变量的值被改变

因此,我们可以使用C预处理器,只需在程序顶部添加这一行:

1
2
3
4
5
6
#define PI 3.1415926
//其通用格式如下
#define NAME value //先写符号常量名,接着是符号常量的值
//一般使用全大写表示符号常量
//也会使用c_和k_前缀表示符号常量
//符号常量命名规则与变量相同(只能大小写字母、数字和下划线,第一个字符不能是数字。尽量避免下划线开头)

同时define指令也可以定义字符和字符串常量,前者单引号后者双引号

注意define指令不需要”=”

const限定符

也可以不使用define而使用const限定符将一个变量限定为只读

1
const int MONTHS = 12;	//MONTHS在程序中不可修改,值为12

明示常量

C的头文件limits.h和float.h分别提供了与整数类型和浮点类型大小限制的详细信息

如limits.h头文件包含如下代码

1
2
3
#define INT_MAX +32767
#define INT_MIN -32767
//注:在32位系统中,INT_MAX = +2147483647,上面数字只是示例

limits.h: https://www.runoob.com/cprogramming/c-standard-library-limits-h.html

float.h: https://www.runoob.com/cprogramming/c-standard-library-float-h.html

printf() 和 scanf()

它们是输入/输出函数,或I/O函数

两个函数工作原理几乎相同,都是用格式字符串和参数列表

printf()

打印数据的指令要和待打印的数据类型相匹配,如打印整数时使用%d。这些符号被称为转换说明

转换说明

  • %a和%A:浮点数、十六进制数和p计数法
  • %c:单个字符
  • %d:有符号的十进制整数
  • %e和%E:浮点数,e计数法
  • %f:浮点数,十进制计数法
  • %g和%G:自动选择%e或%f。%e用于指数小于-4或者大于或等于精度时
  • %i:和%d相同,有符号十进制整数
  • %o:无符号八进制整数
  • %p:指针
  • %s:字符串
  • %u:无符号十进制整数
  • %x和%X:无符号十六进制整数,使用0f或0F
  • %%:打印一个百分号

使用printf

printf函数的格式:printf(“格式字符串”, 待打印项1, 待打印项2……)

格式字符串时双引号括起来的内容;待打印项可以是变量、常量,甚至是打印之前先要计算的表达式

1
2
printf("Farewell!\n");	//可以不用转义序列
printf("%c%d\n", '$', 2 * cost);

要打印%,转义序列是%%

printf()的转换说明修饰符

在%和转换字符之间插入修饰符可以修改基本的转换说明

printf中的修饰符

  • 标记:见下一条注释
  • 数字:最小字段宽度。如果输出的字符数小于这个,则(在前面)补上空格,如果输出字符数大于这个则无影响
  • .数字:精度。
    • 对于%e、%E、%f,表示小数点右边数字的位数,超出一般四舍五入
    • 对于%g和%G,表示有效数字最大位数
    • 对于%s,表示待打印字符的最大数量(如果长度小于这个就打印整个字符串,大于则只输入前面的)
    • 对于整型,表示最小位数,如果不足最小位数就在前面补0
    • .后不接数字等于.0
  • h:和整型一起使用表示short int或者unsigned short int
  • hh:和整型一起使用,表示signed char或unsigned char
  • j:和整型一起使用,,表示intmax_t或uint_max(此类型定义在stdint.h中)
  • l:和整型一起使用,表示long int或unsigned long int
  • ll:和整型一起使用,表示long long int或者unsigned long long int
  • L:和浮点一起使用,表示long double
  • t:和整型一起使用,表示ptrdiff_t(两个指针差值的类型)
  • z:和整型一起使用,表示size_t(sizeof返回的类型)

没有float的转换类型,因为float被自动转换成了float类型

printf中的标记

-:左对齐,此时补空格补在右边

+:显示有符号值的正负(在前面加上+/-)

空格:类似+,但是+换成空格

#:对于非零八进制或者十六进制,加上0或者0x和0X;对于浮点数,即使小数点后没有数字,也打印小数点

0:对于数值格式,用0代替空格填充;如果是整数格式且出现了-或者指定了精度,则不影响

转换说明的意义

转换说明将以二进制形式储存在计算机中的值转换成一系列字符(字符串)以便展示(不会改变原始值,类似翻译)

转换不匹配

转换说明与数据类型不一致则会出现错误,具体错误略

参数传递

与计算机数据结构有关,此处略

printf()的返回值

printf()函数有一个返回值,是其打印字符的个数

1
2
3
4
5
int a = 100;
int rv;

rv = printf("12345and%d\n", a);
printf("%d", rv);

第二行会输出12,因为第一行共打印了12个字符(包括换行符)。但是字符串末尾的\0空字符不会被包括

打印较长的字符串

四种方法断行:

  1. 在参数之间断为两行(不要在双引号的字符串中间断开)
  2. 使用多个printf,前面的不用\n
  3. 在双引号内用\和enter来换行,注意下一行必须顶格
  4. 将一个双引号拆分成多个双引号,中间可以加空格换行等等

使用scanf

如果用其读取基本变量的类型,则需要在变量名前加上&

如果用其读取字符串,则不需要加上&

1
2
scanf("%d", &age);
scanf("%s", name);

scanf使用空白(空格、换行符、制表符)将输入分成多个字段,依次输入(中间只需要至少一个即可,但是%c例外)

scanf的转换说明与修饰符

与printf几乎相同。不同的在下方列出

scanf特殊的修饰符

*:抑制负值

数字:输入达到最大字段宽度,或遇到空白字符时停止

从scanf角度看输入

挨个字符读取,例如%d类型输入12a3,会到a时停止读取,并把a3放入缓冲区,最终输入12

格式字符串中的普通字符

如果代码如下

1
scanf("%d,%d", &m, &n);

则必须按以下格式输入“[数字],[数字]”,即需要符合格式字符串

scanf会跳过整数前面的空白,所以逗号后可以加入空格、换行符、制表符

特殊:%c

除了%c,scanf会自动跳过输入的空白,所以”%d,%d”等价于”%d, %d”

而对%c,如果%c前面没有空白,则会从第一个字符开始读取;而如果有空白,则会从第一个非空白字符开始读取

scanf()的返回值

scanf()函数返回成功读取的项数,如果没有成功读取则返回0

可以使用scanf的返回值来检测(和处理)不匹配的输入

printf()和scanf()的*修饰符

printf()

用于通过程序指定字段宽度或者浮点精度

1
2
printf("Width is |%*d|\n", width, number);	//指示字段宽度
printf("Weight = %*.*f\n", width precision, weight); //指示字段宽度和精度

scanf()

用于跳过读取

1
scanf("%*d %*d %d", &n);	//此时输入三个数字,只有最后一个会被存入n

printf()的用法提示

  1. 要想打印的数据整齐,可以使用足够大的固定字段宽度,如%9d
  2. 在两个打印的字符中间插入一个空白,可以避免因为数字超过字段宽度而导致两个数连在一起的情况
  3. 如果要在文字中嵌入一个数字,则字段宽度应该小于该数字长度,避免空白导致的不美观

练习

练习1、2、5、6、7五道题,依旧很简单

五、运算符、表达式和语句