2020——C的学习笔记(gcc 9.2.0 32位)

程序

1. 程序是对数据的运算。
2. 仅数据的类型决定其在内存中的布局与大小。

程序结构

主函数

1. 形式为 int main(){语句块}
2. 程序仅仅执行主函数,从语句块第一条语句开始,执行完所有语句或遇到 return 关键字结束。但可以在主函数中对其它函数进行嵌套调用,如 printf() 函数。

预处理指令

预处理指令在正式编译前,由预处理程序执行完成。

#include

文件包含命令,用法为 #include <c文件路径>#include "c文件路径"。使用<> 时,编译器会在系统路径中寻找文件;使用" " 时,则会优先在编译器安装目录下的 include 文件夹下寻找,找不到的话再去系统路径中找。
包含标准头文件时用<>,包含自定义头文件时用" ",会是一个好习惯。
在控制台编译时,可以使用 gcc 命令的 -I 选项增加搜寻路径,那么编译器会优先在所增加的路径中寻找。

gcc -I 路径 

#define、#undef

宏定义命令,用法为#define 宏名 字符序列 ,其中字符序列可以为数字、表达式、if 语句、函数等。它让我们可以给一个需要频繁使用到的表达式起一个名字,从而简化代码。
在预处理过程中,代码中的所有宏会被简单替换成宏定义时的字符序列,称为 宏展开 ,除非超出了宏的作用域。
#undef表示宏的作用域到此为止。

#if、 #ifdef、 #ifndef、 #elif、 #else、 #endif

条件编译命令,其中#ifdef 宏名 表示如果定义了宏则执行语句下面的程序段,#ifndef 宏名 反之。其余略。

#line

用法为#line 整数行 文件名 ,其中 行数文件名 顺序不能对调,且文件名要加" "。本质上是对__LINE____FILE__重新赋值。

int main(){
	#line 9 "a.c"
	printf("%d\t%s", __LINE__, __FILE__);
}
9       a.c

#error、#warning

手动抛出错误或警告。

数据类型

变量的声明与定义

格式:type variable_name1 = value, variable_name2 = value;

	extern int a;
	int b=1, c;

左值与右值

lvalue:指向内存位置的表达式,可以在赋值运算符左边或者右边。
rvalue:存储在内存中的值,只能在赋值运算符右边,被赋给左值。

	int b=1;
	2 = b;

提示错误:

error:lvalue requires as left operand of assignment

整数类型

1. char 和 int、unsigned int 。
2. 其中 char 可细分为signed char与unsigned char。
3. int 可细分为 short(2字节) 与 long(4字节) ,而 unsigned int 与 int 不是包含关系,而是并列, int 等同于 signed int 。
4. unsigned int 可细分为 unsigned short 与 unsigned long 。
5. 其中unsigned顾名思义,无符号的。整数的符号,首先想到的也就只有负号了。unsigned作为修饰其含义为 非负的

	unsigned char a = -1;
	unsigned short b = -1;
	unsigned long c = -1;
	printf("%d\n%u\n%lu\n",a,b,c);
255
65535
4294967295

注意:输出时,printf() 函数的格式化说明符需与变量类型保持对应,否则会发生类型转换。
另外,%lu处若为%d%u均会提出警告,表示arguments of type 'long unsigned int'不应对应 format '%d'format '%u'
%u处为%d时却不会提出警告,明明 argument b 也不是 format '%d'expecttype 'int'。猜测是因为 (signed) int 的取值范围必能包含(signed) short 的取值范围,故无需警告。

6. char、int、short、long的取值范围均包含正与负,赋正值或赋负值都可。

    char a = -1;
	int b = -1;
	short c = -1;
	long d = -1;
	printf("%d\n%d\n%d\n%d",a,b,c,d);
 -1
 -1
 -1
 -1

7. 各整数类型的取值范围首先取决于其长度,再根据是否 unsigned 修饰进行进一步运算即可。

	char a;
	printf("char型占用内存:%d byte,故unsigned char型取值范围为:0~%d\n",sizeof(a),(int)(pow(2,(int)(8*sizeof(a))))-1);
	
	short b;
	printf("short型占用内存:%d byte,故unsigned short型取值范围为:0~%d\n",sizeof(b),(int)(pow(2,(int)(8*sizeof(b))))-1);
	
	long c;
	printf("long型占用内存:%d byte,故unsigned long型取值范围为:0~%d\n",sizeof(c),(int)(pow(2,(int)(8*sizeof(c))))-1);
char型占用内存:1 byte,故unsigned char型取值范围为:0~255
short型占用内存:2 byte,故unsigned short型取值范围为:0~65535
long型占用内存:4 byte,故unsigned long型取值范围为:0~2147483646

int型取值范围为short与long的合集。

浮点数类型

	float a;
	double b;
	long double c;
	printf("float型占用内存:%d byte\ndouble型占用内存:%d byte\nlong double型占用内存:%d byte\n",sizeof(a),sizeof(b),sizeof(c));
	printf("float型取值范围为:%e~%e\ndouble型取值范围为:%e~%e\nlong double型取值范围为:%e~%e\n",FLT_MIN,FLT_MAX,DBL_MIN,DBL_MAX,LDBL_MIN,LDBL_MAX);
	printf("float型的精度为:%d\ndouble型的精度为:%d\nlong double型的精度为:%d\n",FLT_DIG,DBL_DIG,LDBL_DIG);
float型占用内存:4 byte
double型占用内存:8 byte
long double型占用内存:12 byte
float型取值范围为:1.175494e-038~3.402823e+038
double型取值范围为:2.225074e-308~1.797693e+308
long double型取值范围为:-0.000000~-1.#QNAN0
float型的精度为:6
double型的精度为:15
long double型的精度为:18

其中1.#QNAN0表示超出了计算机可表示的范围,也就是说我这里应该弄错了什么,但是目前还不太明白,暂略。

void类型

1. 函数无返回值时,函数类型应为void。
2. 函数无入参时,圆括号内应写void。

数组

1. 定义数组时,必须确定它的长度。

	int a[];

提示错误:

error: array size missing in 'a'

因为编译器无法确定a在内存中的大小,也就无法为它分配存储空间。

2. 要确定长度,可以直接赋值,或者声明其长度。

	int a[] = {1,2,3}, b[10];
	printf("a的长度为:%d\nb的长度为:%d\n", sizeof(a), sizeof(b));
a的长度为:12
b的长度为:40

当然也可以既赋值又声明长度,其中长度决定大小:

	int a[4]={1,2,3};
	printf("a的第4个值是:%d\n", a[3]);
a的第4个值是:0

定义int型数组时,如果对部分元素赋值,那么未被赋值的元素,会默认赋值为0。如果是char型数组,则会是\0 (ASCII码中编号为0的字符)。

3. 可以使用下标对数组中的值进行存取,第1个元素的下标为0。

	int a[] = {1,2,3}, b[10];
	printf("数组a中的值依次为:%d\t%d\t%d\n", a[0], a[1], a[2]);
	a[0] = 9;
	a[1] = a[0];
	a[2]++;
	printf("数组a中的值依次为:%d\t%d\t%d\n", a[0], a[1], a[2]);
数组a中的值依次为:1    2       3
数组a中的值依次为:9    9       4

这里不知为何两个\t输出的制表符长度不一样,暂略。

4. 字符串

C语言处理字符串时,会将\0作为字符串的结尾。比如printf()函数:

	char a[10] = {'a', 'b', 0, 'c', 'd'};
	printf("字符串a:%s,长度为:%d,占用空间为:%d Byte\n", a, strlen(a), sizeof(a));

	a[2] = 1;
	printf("字符串a:%s,长度为:%d,占用空间为:%d Byte\n", a, strlen(a), sizeof(a));
字符串a:ab,长度为:2,占用空间为:10 Byte
字符串a:abcd,长度为:5,占用空间为:10 Byte

找不到的话会一直输出,直到找到\0为止:

	char a[5] = {'a', 'b', 'c', 'd','e'};
	printf("字符串a:%s,长度为:%d,占用空间为:%d Byte\n", a, strlen(a), sizeof(a));
字符串a:abcdea,长度为:8,占用空间为:5 Byte

输出的结果往往很离谱。
所以,定义字符型数组的时候,我们需要给\0留个位置,数组长度要比我们所需要定义的字符串长度多1位:

	char a[6] = {'a', 'b', 'c', 'd','e',0};
	printf("字符串a:%s,长度为:%d,占用空间为:%d Byte\n", a, strlen(a), sizeof(a));
字符串a:abcde,长度为:5,占用空间为:6 Byte

但大概是特殊问题特殊处理,C语言也提供了属于字符型数组的独特定义方式:

	char a[] = "abcde";
	printf("字符串a:%s,长度为:%d,占用空间为:%d Byte\n", a, strlen(a), sizeof(a));
字符串a:abcde,长度为:5,占用空间为:6 Byte

这种定义方式自动在字符串末尾补上了\0

二维数组

定义时可以一行一行(以数组为单位)赋值,也可以一个一个(以元素为单位)赋值:

	int a[3][3] = {{1,2,3},{4,5,6}}, b[2][3] = {1,2,3};
	
	printf("数组a中未被定义元素的值为:a[2][0]:%d,a[2][1]:%d,a[2][2]:%d\n",a[2][0], a[2][1], a[2][2]);
	printf("数组b中未被定义元素的值为:b[1][0]:%d,b[1][1]:%d,a[1][2]:%d\n",b[1][0], b[1][1], b[1][2]);

无论是哪种,赋值顺序都是 从左到右,从上到下 。未赋值的元素都会被默认赋值为 0 。

数组a中未被定义元素的值为:a[2][0]:0,a[2][1]:0,a[2][2]:0
数组b中未被定义元素的值为:b[1][0]:0,b[1][1]:0,a[1][2]:0

指针

存储地址的变量类型,与取地址运算符&、解引用运算符* 搭配使用。
使用指针来输出字符串的每一个元素:

int main(){
	char name[] = "Vivien Leigh", *p = name;
	for(;*p!=0;p++){
		printf("%c\n",*p);
	}
}
V
i
v
i
e
n

L
e
i
g
h

这里用到了两个特殊的地方。
一是数组类型变量name存储的也是地址,因此可以直接用来给指针变量p赋值,并且是第1个元素的地址,所以,char *p = name 等效于 char *p = &name[0] ;
二是由于数组元素的地址是连续的,因此p++总是能从指向当前元素变为指向其在数组中的下一个元素。

结构体

定义结构体类型的语法为:

struct 结构体类型名 {定义成员属性,不能赋值};

定义结构体变量的语法为:

struct 结构体类型名 变量名 = {填写成员属性值,不能定义};

常常搭配成员访问运算符.和间接访问运算符-> 使用。

int main(){
	struct stu{
		int age;
		char *name;
		char sex;
		float score;
	};

	struct stu stu1[]={
		{12,"张小明",'男',87.5},
		{11,"李小红",'女',90},
		{12,"王小刚",'男',64.5}
	}, *p=&stu1;

	printf("%s的成绩是:%.1f\n%s的成绩是:%.1f\n%s的成绩是:%.1f\n\n", stu1[0].name, stu1[0].score, stu1[1].name, stu1[1].score,stu1[2].name, stu1[2].score);
	
	printf("%s的成绩是:%.1f\n%s的成绩是:%.1f\n%s的成绩是:%.1f\n\n", (*p).name, (*p).score, (*(p+1)).name, (*(p+1)).score,(*(p+2)).name, (*(p+2)).score);

	printf("%s的成绩是:%.1f\n%s的成绩是:%.1f\n%s的成绩是:%.1f\n", p->name, p->score, (p+1)->name, (p+1)->score,(p+2)->name, (p+2)->score);

}
张小明的成绩是:87.5
李小红的成绩是:90.0
王小刚的成绩是:64.5

张小明的成绩是:87.5
李小红的成绩是:90.0
王小刚的成绩是:64.5

张小明的成绩是:87.5
李小红的成绩是:90.0
王小刚的成绩是:64.5

这里stu1[0].name(*p).namep->name三者等价,并且由于运算优先级的缘故,(*p).name 的圆括号不能缺少。
另外,char *name;如果使用 char name[]的话会报错,提示"flexible array member not at end of struct",意为伸缩数组须是结构体的最后一个成员。
按理说用char name[6]; 的话就应该没问题,但是并不:

张小明许的成绩是:87.5
李小红某杉ㄊ牵?0.0
王小刚械某杉ㄊ牵?4.5

张小明许的成绩是:87.5
李小红某杉ㄊ牵?0.0
王小刚械某杉ㄊ牵?4.5

张小明许的成绩是:87.5
李小红某杉ㄊ牵?0.0
王小刚械某杉ㄊ牵?4.5

不明白,暂略。

运算符

算术运算符

/:取商
%:取余
++:整数部分进1

	float a = 2.5;
	printf("%f\n", a);
	a ++;
	printf("%f", a);
2.500000
3.500000

++= 一起使用时,位置不同,执行顺序也不同:

	int a=1, b, c;
	b = a++;
	printf("++在后时,先赋值后自加,所以b为:%d\n", b);

	a = 1;
	c = ++a;
	printf("++在前时,先自加后赋值,所以c为:%d\n", c);
++在后时,先赋值后自加,所以b为:1
++在前时,先自加后赋值,所以c为:2

--:整数部分减1
…其余略。

关系运算符

==、!=、>、<、>=、<=,判断为真时,运算结果为1。

	printf("%d\t%d\t%d\t%d\t%d\t%d\t",1==0,1!=0,1>0,1<0,1>=0,1<=0);
0       1       1       0       1       0

逻辑运算符

&&:与,两边表达式值都为1时,运算结果才为1。
||:或,一边表达式值为1,运算结果即为1。
!:非,表达式值为0时,运算结果为1。

	int a=1, b=1, c=0;
	printf("1&&1:%d\n1&&0:%d\n1||0:%d\n!1:%d\n!0:%d\n",a&&b, a&&c, a||c, !a, !c);
1&&1:1
1&&0:0
1||0:1
!1:0
!0:1

位运算符

&:与
|:或
^:异或,相同时运算结果为0,不同时运算结果为1
~:取反
<<:按位左移,大多数时候相当于*2
>>:按位右移,大多数时候相当于/2

	int a=72, b=10;
	char s[32];
	
	itoa(a,s,2);
	printf("%d以二进制表示为:\n%032s\n", a, s);
	
	itoa(b,s,2);
	printf("%d以二进制表示为:\n%032s\n", b, s);
	
	itoa(a&b,s,2);
	printf("%d和%d以 &位运算 后结果的二进制结果为:\n%032s\n", a, b, s);
	
	itoa(a|b,s,2);
	printf("%d和%d以 |位运算 后得到的二进制结果为:\n%032s\n", a, b, s);
	
	itoa(a^b,s,2);
	printf("%d和%d以 ^位运算 后得到的二进制结果为:\n%032s\n", a, b, s);
	
	itoa(a<<1,s,2);
	printf("%d以 <<位运算 后得到的二进制结果为:\n%032s\n", a, s);
	
	itoa(a>>1,s,2);
	printf("%d以 >>位运算 后得到的二进制结果为:\n%032s\n", a, s);
	
	itoa(~a,s,2);
	printf("%d\t%d\n", a, b);
	printf("%d以 ~位运算 后得到的二进制结果为:\n%032s\n", a, s);
	
	itoa(~b,s,2);
	printf("%d\t%d\n", a, b);
	printf("%d以 ~位运算 后得到的二进制结果为:\n%032s\n", b, s);
72以二进制表示为:
00000000000000000000000001001000
10以二进制表示为:
00000000000000000000000000001010
72和10以 &位运算 后结果的二进制结果为:
00000000000000000000000000001000
72和10以 |位运算 后得到的二进制结果为:
00000000000000000000000001001010
72和10以 ^位运算 后得到的二进制结果为:
00000000000000000000000001000010
72以 <<位运算 后得到的二进制结果为:
00000000000000000000000010010000
72以 >>位运算 后得到的二进制结果为:
00000000000000000000000000100100
72      0
72以 ~位运算 后得到的二进制结果为:
11111111111111111111111110110111
72      0
0以 ~位运算 后得到的二进制结果为:
11111111111111111111111111111111

这里发生了一件奇怪的事情:itoa(~a,s,2); 修改了b的值。
我不明白是为什么,尝试做了一下修改:

	......
	
	int c=10;

	itoa(~a,s,2);
	printf("%d\t%d\t%d\n", a, b, c);
	printf("%d以 ~位运算 后得到的二进制结果为:\n%032s\n", a, s);

	itoa(~b,s,2);
	printf("%d\t%d\n", a, b);
	printf("%d以 ~位运算 后得到的二进制结果为:\n%032s\n", b, s);

输出结果为:

72      10      0
72以 ~位运算 后得到的二进制结果为:
11111111111111111111111110110111
72      10
10以 ~位运算 后得到的二进制结果为:
11111111111111111111111111110101

结果正确。但是c的值错了。我又将c换了几个其它的值试试,还是一样的运行结果,即:
不管c是什么值,在itoa(~a,s,2);执行后,都会被修改为0。
完全不懂为什么。。。暂略。

赋值运算符

注意一下如 ^= 的运算符组合形式即可,略。

其它运算符

sizeof():取变量占用内存大小(以Byte为单位)

	int a, b;
	printf("size of int:%d", sizeof(a));
size of int:4

&:取地址运算符,返回一个地址

	int a, b;
	
	printf("请输入a:");
	scanf("%d", &a);

	printf("请输入b:");
	scanf("%d", &b);
	
	printf("a:%d,b:%d\n", a, b);
请输入a:1
请输入b:2
a:1,b:2

*:解引用运算符,指向一个变量

	int a, b;

	printf("请输入a:");
	scanf("%d", &a);
	printf("请输入b:");
	scanf("%d", &b);

	printf("a:%d,b:%d\n", a, b);

	int *p, *q;
	p = &a;
	q = &b;

	*p = 10;
	*q = 20;
	
	printf("a:%d,b:%d\n", a, b);
请输入a:1
请输入b:2
a:1,b:2
a:10,b:20

a?x:y :三目运算符,若a表达式返回值为1,则x,否则y。

	int a, b=1;

	a = (1<0)?10:20;
	printf("a被赋的值:%d\n", a);
	a = (1>0)?10:20;
	printf("a被赋的值:%d\n", a);

	a = b?0:1;
	printf("a被赋的值:%d\n", a);
	a = a?0:1;
	printf("a被赋的值:%d\n", a);
a被赋的值:20
a被赋的值:10
a被赋的值:0
a被赋的值:1

条件语句

待补充。

函数

待补充。


版权声明:本文为water_vento原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。