第1章 C语言概述 第2章 C语言基本概念本篇笔记参考了《C语言程序设计-现代方法》和浙大翁恺的C语言视频。
- %f默认输出6个小数
- 在编译时,编译器用空格替代每条注释
- 一个数字,无小数点默认为int型,有小数点默认double型
返回:输出的字符数
%[flags][width][.prec][hil]type
| flag | width或.prec | hil |
|---|---|---|
| -:左对齐,和width同时使用 | number:最小字符数 | hh:单个字节 |
| +:在前面放+ | *:下一参数是字符数 | h:short |
| (space):正数留空 | .:number小数点后的位数 | l:long |
| 0:0填充 | .*:下一参数是小数点后的位数 | ll:long long |
| L:long double |
| type | 含义 | type | 含义 |
|---|---|---|---|
| i或d | int | g或G | float |
| u | unsigned int | a或A | 十六进制浮点 |
| o | 八进制 | c | char |
| x | 十六进制 | s | 字符串 |
| X(大写) | 字母大写的十六进制 | p | 指针 |
| f或F | float, 6 | n | 读入/写出的个数 |
| e或E | 指数 |
printf("%9dn", 123); //123
printf("%.9dn", 123); //000000123
printf("%+9dn", 123); //+123
printf("%-9dn", 123); //123
printf("%+-9dn", 123); //+123
printf("%9.2fn", 123); //0.00,前后格式不对
printf("%9.2fn", 123.0); //123.00
printf("%-*.2fn", 9, 123.0); //9代入*的位置
printf("%-9.*fn", len, 123.0); //len这个变量代入*的位置
printf("%hhdn", (char)12345); //12345转化为十六进制为0x3039,单字节(低位)为0x39,即十进制的57
#include#include int main() { int i; for (i = 0; i <= 100; i++) { printf("Percent completed: %3d%%r", i); Sleep(1000); } return 0; }
- 显示%号,用printf(“%%”);
返回:读入的项目数
- 当scanf函数读到不符合要求的字符时,会把该字符“放回原处”,以供下次使用。看下面程序,输入1-20.3-4.0e3,输出i = 1, j = -20, x = 0.300000, y = -4000.000000。因为第一个%d要求读入一个数字,而"-“不能跟在数字后面,所以读到第一个”-“号时结束第一个%d的读取。下一个%d从刚才的”-"号开始,读到-20。小数点不是%d应该出现的东西,所以在这里第二个%d结束,得到-20。
- "放回原处"指的是,用户在键盘输入时,数据没有直接被读取,而是存放在缓冲区。然后再由scanf函数决定是否读取。
scanf("%d%d%f%f", &i, &j, &x, &y);
printf("i = %d, j = %d, x = %f, y = %f", i, j, x, y);
%[flag][type]
| flag | 含义 | flag | 含义 |
|---|---|---|---|
| * | 跳过 | l | long, double |
| 数字 | 最大字符数 | ll | long long |
| hh | char | L | long double |
| h | short |
| type | 含义 | type | 含义 |
|---|---|---|---|
| d | int | a, e, f, g | float |
| i | 整数,可能为十六进制或八进制(输入时前面加0x或0) | c | char |
| u | unsigned int | s | 字符串(单词) |
| o | 八进制 | […] | 所允许的字符 |
| x | 十六进制 | p | 指针 |
int number;
scanf("%*d%d", &number); //假设输入123 456
printf("%dn", number); //只输出456
int num;
int i1 = scanf("%i", &num); //输入1234
int i2 = printf("%dn", num); //这里输出1234
printf("%d:%d", i1, i2); //输出1:5(i2包含换行符)
第4章 表达式
4.1
4.2 赋值运算符
-
运算符=是右结合的,所以i = j = k = 0 等价于i = (j = (k = 0))。同时,还会带来类型转化的问题。
-
int i; float f; f = i = 33.f;
-
-
说明 举例 左值 存储在计算机内存中的对象 变量。左值表达式表示了一块内存区域。正因此,可以用&取地址的表达式 右值 -
以下表达式均是错误的
-
12 = i; //错误 i + j = 0; //错误
C99开始,才对布尔值进行了定义。
_Bool flag;
#includebool flag; ## 5.1 逻辑表达式 数字越小,优先级越高。 ****! > 算术运算符 > 关系运算符 > && > || > 赋值运算符**** | 优先级 | | | 结合方式 | 备注 | | ------ | ---------- | ------------- | -------- | ------------------------------------------------------------ | | 1 | | | | | | 2 | | | | | | 3 | | | | | | 4 | | | | | | 6 | | | | | | 7 | 关系运算符 | <, <=,>,>= | 左结合 | i < j < k等价于(i < j) < k,即先判断括号里的真假,再和k比较,再输出真假 | | 8 | 判等运算符 | ==,!= | 左结合 | i < j == j < k等价于(i < j) == (j < k) | | 9 | | | | | | 10 | | | | | | 11 | | | | | | 12 | 逻辑与AND | && | 左结合 | | | 13 | 逻辑或OR | || | 左结合 | | | 14 | | | | | | 15 | | | | | | 16 | | | | | | 17 | | | | | ## 5.2 if语句 **格式:** ```c if (表达式) 单语句; =================== if (表达式) { 单语句; 单语句; } ===================== if (表达式) 语句; else 语句; ===================== if (表达式) 语句; else if (表达式) 语句; else if (表达式) 语句; else 语句;
-
执行语句时, 先计算圆括号内表达式的值。如果表达式的值非零(C语言把非零值解释为真值), 那么接着执行圆括号后边的语句。
-
默认if后面只执行一条语句,多语句记得用大括号!
-
else与最近的且未匹配的if配对,和缩进无关。
条件表达式
if 表达式1?表达式2:表达式3
- 读作 “如果表达式l成立, 那么表达式2, 否则表达式3。”条件表达式求值的步骤是:
- 首先计算出表达式1的值,如果此值不为零,那么计算 表达式2的值, 并且计算出来的值就是整个条件表达式的值;
- 如果表达式1的值为零, 那么表达 式3的值是整个条件表达式的值。
- 如果表达式1是int型,表达式2是float型,那么整个表达式还是float型
if (i > j) return i; else return j; 等价于return i > j ? i : J; ============================5.3 switch-case语句
格式
switch (控制表达式){
case 常量:
语句;
break;
case 常量:
语句;
break;
case 常量:
...
default:
语句;
break;
}
-
switch-case语句可以起到多个else-if语句的功能。不同之处在于
- else-if语句要不断判断条件,直到条件符合。当条件很靠后时,会很费时间。而switch-case可以直接调到相应的case。
- 控制表达式里面的必须为整数型的结果。
- 一个case可以写多条语句,不需要用括号括起来。
-
常量可以是常数,也可以是常数计算的表达式
-
case表示进入的路标,break表示推出switch-case的路标。如果一个case里面没有break,那么程序会继续往下执行直到遇到break。有时故意这样设计以达到多个case公用同样的表达式。见下面的代码。给不同日期加上不同的英文后缀
-
switch (day) { case 1: case 21: case 31: printf("st");break; case 2: case 22: printf("nd");break; case 3: case 23: printf("rd");break; default: printf("th");break; } -
switch语句不要求一定有default分支。如果default不存在,而且控制表达式的值和任何一个分支标号都不匹配的话,控制会直接传给switch语句后面的语句。
格式
while (条件)
语句;
while (条件)
{
语句;
语句;
}
-
类似的if语句,当有多条语句时,采用大括号括起来
-
printf函数本身可以做到循环变量改变的特点,如下
int main()
{
int i = 10;
while (i > 0)
{
printf("T minutes %d and counting.n", i--);
}
return 0;
}
- 上述循环中,在不严格的情况下可以写成下面的形式。但考虑到更普遍的情况,当i初始值不为正数时,旧循环会直接终止,新循环会一直循环下去。
while (i)
printf("T minutes %d and counting.n", i--);
-
如何做到两列整齐的输出?
-
做法是
printf("%10d%10dn", i, i * i);- %10d表示数据宽度为10,不满10前面的不显示,且数据右对齐。
-
i++和++i的区别?
-
i++表示先参与运算,再令i自增。++i表示先自增,再参与运算
-
int i = 1; //打印时,按1 2 3 4 .。。打印 do { printf("%dn", i++); } //=================================== int i = 1; //打印时,按2 3 4 5.。。打印 do { printf("%dn", ++i); }
-
格式:
for (表达式1;表达式2;表达式3)
语句;
//=============
for (表达式1;表达式2;表达式3)
{
语句;
语句3;
}
-
等价于
-
表达式1; //即初始化 while (表达式2) { 语句; 表达式3; } -
可以省略3个表达式中的部分,但此时省略的部分需要在其他地方标明。
-
i = 10; for (;i > 0;--i) { printf("%dn",i) } //============================ for (i = 10;i > 0;) { printf("%dn",i--) }
for (; ;)
{
读入数据;
if (数据的第一条测试)
continue;
if (数据的第二条测试)
continue;
if (数据的第n条测试)
continue;
处理数据;
}
-
**在C99中,允许在表达式1进行声明,且可以进行多个声明。但此声明只能在for循环里使用。**看下面
-
int N = 10; for (sum = 0, i = 1; i <= N; i++) { sum += i; }
-
break:用于跳出当前循环。当有多层循环时,只能跳出当前的一层。能用于switch和循环(while、do-whie和for)。
-
continue:用于跳到当前循环的最末尾,但仍没有跳出循环。只能用于循环(while、do-whie和for)。
n = 0;
sum = 0;
while (n < 10)
{
scanf("%d", &i;)
if (i == 0)
continue;
sum += i;
n++;
}
-
goto:配合标识符使用。【标号语句为】:标识符:语句。【goto语句】:goto 标识符
-
for (d = 2; d < n; d++) if (n % d == 0) goto done; done: if (d < n) printf("%d is divisible by %dn", n, d); else printf("%d is primen", n) -
标识符后的语句必须和goto语句在同一个函数中。
-
有符号数:C语言默认。最左边一维表示符号,0表示正数/零,1表示负数。例如有符号16位的最大值位215-1
-
无符号数:需要用unsigned声明。无符号16位整型的最大值为216-1
-
可用sizeof(int)等语句测试该类型的取值范围。在32和64位机器中,int为4 bytes,所以取值范围为-231~231-1
-
long long int的取值范围是-263~263-1
-
类型声明的组合:
- long/short, signed/unsigned,说明符可自由组合,不分先后顺序,如long unsigned int 等价于unsigned long int。
- int可省略。unsigned short int 等价于 unsigned short。
-
整数常量:
- 十进制:不能以零开头
- 八进制:必须以零开头,如077
- 十六进制:必须以0x开头,后面的字母大小写均可。
- 可在后面加上U/u(无符号)或L/l(长整型)指定数据类型。UL顺序、大小无关紧要。
-
整数溢出:此时仅仅改变类型不够,还要审视其他涉及到此数据的语句,如printf中的%d
-
读写整数:
-
无符号整数 %u
%o
%x十进制
八进制
十六进制短整型 前面加上h %hu
%ho
%hx长整型 前面加上l 长长整型 前面加上ll
-
- IEEE浮点标准里,浮点数由符号、指数、小数三部分组成
| float | 单精度 | 32位 |
|---|---|---|
| double | 双精度 | 64位 |
| long double | 扩展双精度 |
- 只能在scanf函数里面使用l,如下。C99中允许在printf里面写,但视作无效
double d;
scanf("lf", &d);
float annwer = 17 / 13; float answer = 17.0 / 13.0; float answer = 17 / (float) 13; float f; float c = 5 / 9.0 * (f - 32); float c = (f - 32) * 5 / 9;7.3 字符类型
常见字符对应数字
| 字符 | 十进制数 |
|---|---|
| A | 65 |
| a | 97 |
| 0 | 48 |
| 空格 | 32 |
-
字符往往需要用单引号‘ 括起来,如’A’
-
字符常量其实是int类型,而不是char类型。所以可用对char类型的数据进行计算、比较
-
char ch; int i; i = 'a'; //i is now 97 ch = 65; //ch is now 'A' ch = ch + 1; //ch is now 'B' ch++; //CH is now 'C'
if ('a' <= ch && ch >= 'z') ch = ch - 'a' + 'A'; //把小写字母转化成大写字母 //=======相当于以下函数============== #inlcude//使用toupper必须含义此头文件 ch = toupper(ch) //当参数是小写时,返回大写。否则返回本身 -
有符号字符和无符号字符
- 有符号字符:-128~127
- 无符号字符:0~255
- 标准C中允许用signed或unsigned来修饰 char
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yVLEk4S9-1653993643083)(D:DocumentsCimagesimage-20220220201831067.png)]
-
使用%c来在scanf和printf中读写字符
-
当需要跳过空格时,需要写成scanf(“ %c”, ch),注意%c前面有个空格
-
getchar和putchar表示读和写单个字符。getchar函数返回的是一个int类型。getchar函数比scanf函数更有效率。
-
int main() { int length = 0; char ch; printf("Input a sentence:"); while (getchar() != 'n') length++; printf("Your message is %d character(s) long.", length); return 0; } -
getchar惯用法
while (getchar() != 'n')
;
while ((ch = getchar()) == ' ')
;
- scanf函数和getchar函数最好不要混用,因为scanf函数会留下没有消耗掉的字符,比如换行符。下一次getchar读入时,就会读到这个换行符。很容易出错。
赋值转化
- 右边的类型会变成左边的类型。浮点变整型的过程中,小数点丢失,而不是四舍五入。例如
int i; char c; i = 843.56; //now i is 843 i = -843.56; //now i is -843 i = i + c; //c会被转化为
算术转化
- 往容量更大的数据转化。如float——double——long double, int——unsigned int——long int——unsigned long int
强制转化
使用**(类型名)表达式**来强制转化。如
long i; int j = 1000; i = j * j; //有溢出。因为j是int型,j*j还是int型,1000000大于int的范围,i被转化为int通用出问题。 i = (long)j * j;7.5 类型定义
采用类型定义会使得编译器将新类型加入类型列表中
typedef int Bool; Bool flag //same as int flag
- 要点:Bool这里的名词一般以大写开头
- 区别#define预处理。#define原型在后面。如下面
#define BOOL int
-
类型定义的优点:
-
更易理解。如
-
typedef float Dollars; Dollars case_in, case_out;
-
-
更易修改和移植。如要把float修改成double型,只需要把前面修改成type double Dollars;在代码移植过程中,前后机器的类型的取值范围不同,使用类型定义可方便修改数据类型。
-
-
可移植性
不同位机器,如32位机器和16位机器对于int的取值范围是不同的。为了使移植时不出错,那么可使用typedef类型定义来快速修改。这些因实现的不同而改变 (实现定义的, implementation-defined) 的类型名经常以_t为后缀。如下
typedef unsigned long int size_t; typedef int wchar_t;
- 类型定义比宏定义功能更强大。例如,数组和指针类型是不能被定义为宏的。
-
数组声明
-
#define N 10 int a[N];
-
a[i]的表达式是左值,可以当成变量一样使用。如下
-
scanf("%dn", &a[i]); //注意&符号 a[i + j * 10] = 0; -
数组初始化
int a[5] = {0, 1, 2, 3, 4};
int a[10] = {0, 1, 2, 3, 4, 5}; //a[6]~a[10]均为0
int a[10] = {0}; //a[0] = 0, 其他未填默认为0。利用此特性可快速初始化所有元素为0.
int a[] = {0, 1, 2, 3, 4}; //也可以不指定数组的长度
int a[100] = {5, 1, 9, [6] = 9, 56, [12] = 2, [5] = 99}
int a[] = {[6] = 9, 56, [12] = 2}
- 当重复初始化时,会按照原来的顺序继续下去。按从左往右,开始有4, 9, 1, 8;然后[0]=5使得a[0]上的4被替换成5,此时下标继续往下走,所以a[1]=7。最终数组a[4] = {5, 7, 1, 8}.
int a[] = {4, 9, 1, 8, [0]=5, 7};
- 数组的复制问题!
数组a[]和b[]不能直接用赋值符号=来复制,因为底层中数组名和指针相关。一个笨办法是遍历元素逐一复制。另一个办法是使用中的memcpy(内存复制)函数。格式为
memcpy(a, b, sizeof(a));
-
计算数组长度
-
sizeof(a); //计算数组a所占字节数。返回一个无符号类型size_t sizeof(a[0]); //计算元素所占字节数。返回一个无符号类型size_t sizeof(a) / sizeof(a[0]); //得出长度。sizeof返回一个无符号类型size_t (int) (sizeof(a) / sizeof(a[0])); //强制转化为有符号整数,以免报错 #define SIZE (int) (sizeof(a) / sizeof(a[0])) //宏定义,以免下面的代码太难写
-
-
常量数组:数组前使用const可另数组变成常量,不允许修改
-
随机函数srand和rand
void srand(unsigned int seed);
- srand()函数就是用来设置rand()函数的种子的。根据不同的输入参数可以产生不同的种子。通常使用time函数作为srand函数的输入参数。time函数会返回1970年1月1日至今所经历的时间(以秒为单位)。在使用 rand() 函数之前,srand() 函数要先被调用,并且在整个程序中只需被调用一次。
#include#include #include #include #define NUM_SUITS 4 #define NUM_RANKS 13 int main() { bool in_hand[NUM_SUITS][NUM_RANKS] = {false}; int num_card, rank, suit; const char rank_code[] = {'2', '3', '4', '5', '6', '7', '8', '9', 'T', 'J', 'Q', 'K', 'A'}; const char suit_code[] = {'d', 'c', 'h', 's'}; srand((unsigned) time(NULL)); printf("Enter number(s) of cards in hand: "); scanf("%d", &num_card); printf("Your hand: "); while (num_card > 0) { rank = rand() % NUM_RANKS; suit = rand() % NUM_SUITS; if (!in_hand[rank][suit]) { in_hand[rank][suit] = true; num_card--; printf("%c_%c ",rank_code[rank], suit_code[suit]); } } return 0; }
- 变长数组(C99 only):
scanf("%d", &n);
int a[n];
int b[3 * n +5];
8.2 二维数组
逗号运算符
格式
表达式1,表达式2
计算过程:分别计算处表达式1和2的值,并且把表达式2作为整个表达式的值。
-
二维数组的声明:
int a[m][n]; //该声明产生m行n列的二维数组
-
访问第m行n列的元素时,写成a[m][n]的形式,不能写成a[m, n](不要和数学上的混淆),否则方括号里面的会被当成逗号运算符,a[m, n] == a[n]。
-
二维数组在内存中是按行排列的,即先排完第1行的内容,再排第二行的内容。
-
二维数组的初始化
-
int m[5][9] = {{1,2,3,4,5,6,7,8,9},{2,3,4,5,6,7,8,9,10}}
格式
-
double是函数返回的类型。不带返回值时,前面为void。当返回类型很长,如unsigned long int时,返回来行可单独成一行。如
-
unsigned long int average(double a, double b)
-
-
函数名为average
-
a和b为形参(parameter),每一个形参都需要单独说明类型。没有时写void。
-
{ }括起来的部分为函数体
-
函数只能返回一个值
double average(double a, double b)
{
return (a + b) / 2;
}
- 当调用函数时,需要写出函数名及跟随其后的实际参数(argument)列表。如average(x,y)的效果就是把变量x和y的值复制给形式参数a和b。实际参数不一定是变量,如average(3, 5)
- return 0 时,表示没有错误;return 1时,表示有错误
int line;
printf("How many lines?n");
scanf("%d", &line);
if (line < 1)
{
printf("Sorry, that makes no sense.n");
return 1;
}
- 问:不使用函数原型,直接把函数定义都放在main函数前面可以吗?
- 答:当只有main函数调用其他函数时可以。但其他函数有相互调用的情况时,要斟酌其他函数的顺序。否则会出现函数未定义的情况。所以最稳妥的方法是先放函数原型,在main函数之后放函数定义。
- 问:函数声明中,指定一维数组的形式参数的长度,会怎么样?
- 答:编译器会忽略长度值。例如在int product(int v[3], int w[3])中,虽然程序编写者想提示应该传长度为3的数组进函数,实际可以传任意长度的数组。
格式
返回类型 函数名(形式参数); double average(double a, double b); double average(double, double); //也可行,
C语言要求在调用函数时编译器已经知道函数,所有有两种方法
- 把函数定义放在main函数前面;
- main函数前面写函数声明,main函数之后再写函数定义。
-
形式参数(parameter) 出现在函数定义中 实际参数(argument) 出现在函数调用的表达式 - 参数类型的转换:按照实际参数来。如调用时用了double,形参是int,那么按int来。
void square(int x);
int main()
{
double x = 2.5;
square(x);
return 0;
}
void square(int x)
{
printf("square(x) = %dn", x * x);
}
9.3 数组型实际参数
数组经常被当作实际参数,当形式参数是一维数组时,可以(实际经常这么做)不说明数组的长度。如果需要知道数组的长度,必须传入相关的参数。
int f(int a[])
{
...
}
int f(int[], int)
{
...
}
int sum(int a[][LEN], int n);
int sum_array(int a[], int n); total = sum_array(a, n);
- 因为数组当形参时,是用指针来传递的,不是通过复制形参作为实参的,所以调用函数之后可以改变形参中的数组。在下面的例子中,传入的数组a[]的元素被置为0了。
void store_zero(int a[], int n)
{
int i;
for (i = 0; i < n; i++)
a[i] = 0;
}
store_zero(b, 100);
- 可变长度数组作形参
int sum_array(int n, int a[n])
{
...
}
int sum_array(int n, int m, int a[n][m])
{
...
}
int sum_array(int n, int a[n]);
int sum_array(int n, int a[*]); //用*代替长度 */
int sum_array(int, int [*]);
int sum_array(int n, int a[]) //函数中空
int sum_array(int, int []);
int sum_array(int a[static 3], int n) //表面数组a的长度至少是3
{
...
}
-
如果数组是多维的,static仅可用于第一维(行数)
-
数组中的复合字面量
格式
(给定类型){元素的值}
int b[] = {3, 0, 3, 4, 1}; total = sum_array(b, 5); total = sum_array((int []){3, 0, 3, 4, 1}, 5); total = sum_array((int []) {2*i, i+j, j*k}, 3);
格式
return 表达式;
- 当return语句的表达式和函数返回类型不匹配时,会被强制转化成函数的返回类型。
- return可以用在void类型的函数中,非必需。
return;
main函数
- main函数返回的是状态码,return 0;程序正常终止;为了表示异常终止,可以返回其他值。
exit函数
-
exit函数也可用于终止程序。
#include
//EXTIT_SUCCESS和EXIT_FAILURE定义在此宏中,使用时要加上 exit(0); exit(EXTIT_SUCCESS); exit(EXIT_FAILURE); 函数 说明 return 仅当main函数调用时才会导致程序终止 exit 任何函数调用都会导致程序终止
函数可以调用它本身
int fact(int n)
{
if (n <= 1)
return 1;
else
return n * fact(n-1);
}
int fact(int n)
{
return n <= 1 ? 1 : n * fact(n-1);
}
- 运用分治法(divide-and-conquer)实现快速排序。快速排序的步骤为:
- 选定一个参照元素ref;
- 将数组中小于ref的数放在左边;
- 将数组中大于ref的数放在右边。
- 进行一次排序后,得到 | . . . | ref | . . . |左右两个小区间。左边区间的数都比ref小,右边都比ref大。然后运用递归的思想对小区间进行快速排序。程序实现如下
void quicksort(int a[], int left, int right)
{
if (left < right)
{
int i = left, j = right, ref = a[left];
while (i < j)
{
while (i < j && a[j] >= ref)
j--;
if (i < j)
a[i++] = a[j];
while (i < j && a[i] <= ref)
i++;
if (i < j)
a[j--] = a[i];
}
a[i] = ref;
quicksort(a, left, i - 1);
quicksort(a, i + 1, right);
}
}
第10章 程序结构
10.1 局部变量
定义:函数体内声明的变量称为该函数的局部变量。
自动存储期限(storage duration):局部变量的存储单元在函数被调用时“自动”分配,在函数返回时收回分配,。
- static关键字的含义:
- 用于隐藏。用static定义的变量和函数,只能在它所在的文件/函数内被访问。在同时编译多个文件时,未加static的全局变量和函数具有全局可见性;
- 只初始化一次;
- 未手动初始化时,默认初始化为0;自动变量位于栈区,加了static后就到了“静态数据区”,这里的所有字节默认为0x00;
char a = 'A'; // global variable
void msg()
{
printf("Hellon");
}
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序运行结果:A Hello因为未加static所以a.c中的变量和函数能在main.c中被使用
#includeint fun(void){ static int count = 10; return count--; } int count = 1; int main(void) { printf("globalttlocal staticn"); for(; count <= 10; ++count) printf("%dtt%dn", count, fun()); return 0; }
上述程序运行结果:因为只初始化一次,fun()不会一直返回10
global local static 1 10 2 9 3 8 4 7 5 6 6 5 7 4 8 3 9 2 10 1
块作用域:所以其他函数可以把同名变量用于别的用途。
静态局部变量:在变量声明中使用static,可以使变量具有静态存储期限而不是自动存储期限。拥有静态存储期限的变量获得永久的存储单元,不会丢失其值。但身为局部变量,有块作用域,静态局部变量仍不能被其他函数可见。
- 形式参数和局部变量很相似,区别是每次函数调用形式参数时,会对其初始化。
f(); //打印1和3
f(); //打印3和5
f(); //打印5和7
int f()
{
static int all = 1;
printf("%dn", all);
all += 2;
printf("%dn", all);
return all;
}
全局变量又称为外部变量。全局变量的特点
静态存储期限:其中的值永久被保留
文件作用域:从变量被声明的点到文件末尾,所有函数都可访问(并修改)它。
初始化:只能用常量来初始化,不能用一个变量来初始化。
注意:如果函数内有和全局变量同名的局部变量,那么全局变量会被隐藏。离开该函数,全局变量就恢复。
10.3 程序结构#include指令 #define指令 类型定义 外部变量的声明 除main函数之外的函数原型 main函数的定义 其他函数的定义第11章 指针 11.1 指针变量
&:取得变量的的地址,操作的对象必须是变量。下面的表达是错误的
&(a++); &(--a);
指针变量:专门用于存放地址的变量。如果用其它变量存放(比如int a),可能会因为强制转化把地址丢失掉。
| 指针变量的值 | 具有实际值的变量的地址 |
|---|---|
| 普通变量的值 | 实际的值 |
注意:
- 指针即地址
- *和&是一对互为相反的操作
- 不是说加了*才是指针,正常命名如p也可以是指针
- 在声明时,必须加*表示后面的p是指针
- 输入出书时,根据地址/指针用%p,指针指代的变量用其他如%d等。
- 建议声明时就令*p为零。
#includevoid f(int *p); int main() { int i = 100; printf("&i = %pn", &i); f(&i); return 0; } void f(int *p) { printf("p = %pn", p); printf("*p = %dn", *p); }
&i = 000000000061FE1C p = 000000000061FE1C *p = 100
*是一个单目运算,用来访问指针的值所表示的地址上的变量。可以作左值也可以作右值。
int k = *p; *p = k + 1;
指针应用场景一:交换两个值
#includevoid swap(int *pa, int *pb); int main() { int a = 5, b = 6; swap(&a, &b); printf("a = %d, b = %d", a, b); return 0; } void swap(int *pa, int *pb) { int t = *pa; *pa = *pb; *pb = t; }
指针应用场景二:返回多个值
#includevoid minmax(int a[], int len, int *max, int *min); int main() { int a[] = {5, 4, 0, -9, 100, 66, 7}; int min, max; minmax(a, sizeof(a)/sizeof(a[0]), &min, &max); printf("min = %d, max = %d", min, max); return 0; } void minmax(int a[], int len, int *max, int *min) { int i; *min = *max = a[0]; for (i = 0; i < len; i++) { if (a[i] < *min) *min = a[i]; if (a[i] > *max) *max = a[i]; } }
指针应用场景二b:函数返回运算的结果,结果通过指针返回
#includeint divide(int a, int b, int *result); int main() { int a = 5; int b = 3; int c; if (divide(a, b, &c)) //1为真,则能进行除法 printf("%d/%d=%d",a,b,c); return 0; } int divide(int a, int b, int *result) { int ret = 1; if (b == 0) ret = 0; else *result = a/b; return ret; }
注意:定义了指针,在没有指向任何变量前,不要对齐进行赋值。否则可能会把内存中不知名的区域数据进行了更改。
11.2 指针作为返回值
int *max(int *a, int *b)
{
if (*a > *b)
return a;
else
return b;
}
注:不要返回执行自动局部变量的指针,如
int *f(void)
{
int i;
...
return &i
}
第12章 指针和数组
- 指向数组的指针相当于指向数组的第一个元素。
在函数参数表中,数组即指针。以下四个表达式在参数表中是等效的
int sum(int *ar, int n); int sum(int *, int); int sum(int ar[], int n); int sum(int [], int);
int a[10], *p = a; //p[i]这样的写法没有问题,因为指针可以用作数组名
由于在函数参数中的数组是以指针存在的,所有在函数中用sizeof(a)==sizeof(int *),即不能用sizeof(a)/sizeof(a[0])来计算数组长度。
int a[10]; int *p = a; a == &a[0]; p[0] == a[0]; *a = 25;
数组变量是const的指针,所有不能被赋值。如下
int b[]; b = a;12.1 const的问题
-
指针是const:表示一旦得到了某个变量的地址,不能再指向其他变量
int * const q = &i; //q是const *q = 26; //OK q++; //ERROR
-
所指是const:不能通过这个指针修改那个变量。变量可以变,指针可以变,但不能修改.
const int *p = &i; *p = 26; //ERROR i = 26; //OK p = &j; //OK
int i; const int* p1 = &i; int const* p2 = &i; int *const p3 = &i;
例子1:
int a = 10;
int b = 20;
const int *c = &a; //const修饰的是int,也即是*c的值不可变,但c指针可变
//int const *d = &a; //同上句代码作用等同
/
p = &j;
}
void func1(const int * const p)
{
int j;
*p = 0;
p = &j;
}
- 数组是const:用来保护数组不被破坏
int sum(const int a[], int length)12.2 指针运算
C语言只支持3种指针运算
- 指针加上整数
- 指针减去整数
- 两个指针相减(两个指针指向同一个数组时才有意义)
指针+1的结果取决于指针的类型。
sizeof(char) = 1; sizeof(int) = 4;
char ac[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
char *p = ac;
printf("p = %pn", p);
printf("p+1 = %pn", p+1);
int ai[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *q = ai;
printf("q = %pn", q);
printf("q+1 = %pn", q+1);
p = 000000000061FE06 p+1 = 000000000061FE07 q = 000000000061FDD0 q+1 = 000000000061FDD4
*(p+1) ==>ac[1]; *(p+n) ==>ac[n]; *(q+n) ==>ai[n]; *q + 1; //该运算往往没有实际意义,*q+1=000000000061FDD1,没有意义
- *p++
char ac[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1};
char *p = ac;
for (p = ac; *p != -1; p++)
printf("%dn", *p);
for (p = ac; *p != -1; p) //上面的替换写法
printf("%dn", *p++);
while (*p != -1) //上面的替换写法
printf("%dn", *p++);
-
NULL:零地址
-
无论指向什么类型,所有的指针大小都是一样的,因为都是地址;但指向不同类型的指针不能相互赋值,例如上面的例子中,另char的p指针和int的q指针相等,那么另*q = 0的时候,char的数组的连续4个元素都要变成0
-
指针的类型转换:
-
int a[N], *P; for (p = a; p < a + N; p++) scanf("%d", p)
malloc:分配内存空间(byte为单位),与free搭配使用
#includevoid* malloc(size_t size);
int *x; x = malloc(sizeof(int)); *x = 42;
- 使用时要添加头文件
- 返回的是void* 需要类型转换,如(int*)(n*sizeof(int))
free():把申请来的空间还给“系统”,只能还申请得来空间的首地址。(如p是申请来的,p++后,free§是不允许的。
12.4 指针和多维数组-
原则上可把二维数组当成一维数组来处理。如用for循环把所有元素置0,不需使用两层for,使用一层也可以。
对于二维数组而言,p[i]表示第i行的首地址,p[i]+j表示第i行地点j列的元素。
int a[row][col], *p; //假设有row行col列的二维数组
for (p = &a[0][0]; p <= &a[row-1][col-1]; p++)
*p = 0;
-
处理多维数组的行
p = &a[i][0]; //p为指针 p = a[i]; //上述程序的简写
-
处理多维数组的列
int a[row][col], (*p)[col], i; for (p = &a[0]; p < &a[row]; p++) (*p)[i] = 0;
在初始化二维数组时,需要把二维数组的列说明
char a[][10] = {"hello", "world"};
二维数组指针举例:
#include12.5 指针数组int main() { int a[3][4], *p; int i, j; p = a[0]; for (i = 0; i < 3; i++) for (j = 0; j < 4; j++) scanf("%d", p++); p = a[0]; for (i = 0; i < 3; i++) { for (j = 0; j < 4; j++) printf("%4d", *p++); printf("n"); } return 0; }
意义:一个数组,里面存放的全是指针
格式:类型说明符 * 数组名[数组长度]
如: char *str[4];
注意:
int *ptr[5]; int (*ptr)[5];第13章 字符串 13.1 基础知识
字符串常量:以双引号“”括起来的序列,C语言中称为字符串字面量。以 ‘ ’为结束的字符数组。
char ch;
ch = "abc"[1]; //ch的值为b
char digit_to_hex_char(int digit)
{
return "0123456789ABCDEF"[digit];
}
惯用法:
#define STR_LEN 80 ... char str[STR_LEN+1];
int main()
{
int a;
char *s = "Hello World!";
printf("%sn", &s[6]);
printf("%cn", *s);
return 0;
}
字符串变量:
由于字符串以数组的形式存在,所以下方传递
- 在计算机底层中,数组又是和指针(地址)息息相关所以,若要比较两个字符串是否相同,如果用if (s1 == s2),比较的是两个地址。
#includeint main() { char s1[10], s2[10]; printf("input the first string: "); scanf("%s", s1); printf("input the second string: "); scanf("%s", s2); if (s1 == s2) { printf("string1 == string2n"); printf("%sn", s1); printf("%sn", s2); } else { printf("string1 != string2n"); printf("the address of %s is %pn", s1, s1); printf("the address of %s is %pn", s2, s2); } return 0; }
- 以0(整数0)(又称空字符)结束的一串字符。注意,0和‘ ’是一样的,和‘0’不一样,后者是ASCIII码的字符0。
char *s1;
scanf("%s", s1);
char *s2 = malloc((strlen(s1) + 1) * sizeof(char));
int n = sizeof(strlen(s1));
for (int i = 0; i < n; i++)
s2[i] = s1[i];
s2[n] = ' ';
- 0标志着字符串的结束,但不是字符串的一部分。计算字符串长度的时候不包含0。
- 字符串以数组的形式存在,以数组或指针的形式访问。更多是以指针的形式。
- string.h里面有很多处理字符串的函数
- 用双引号引出来的脚字符串字面量(字符串常量),可以用来初始化字符数组。
char *str = "hello";
char word[] = "hello";
char word[10] = "hello";
printf("hello""world");
printf("hello,
world");
printf("hello,
world");
printf("%s", __func__);//返回函数名,两个下横线
字符串常量:
- 如果要修改子字符串,应该用数组形式声明。如s3[]
- 数组:这个字符串在这里,作为本地变量被回收
- 指针:这个字符串不知道在哪里,可用来处理参数和动态分配空间
int i;
char *s = "hello";
char *s2 = "hello";
char s3[] = "hello";
printf("&i = %pn", &i);
printf("s = %pn", s);
printf("s2 = %pn", s2);
s3[0] = 'H';
printf("s3 = %pn", s3);
printf("here, s3[0] = %cn", s3[0]);
-
两个地址一样,是因为char *s 相当于 const char *s,由于历史原因,编译器接受不带const的写法。
-
putchar和getchar的使用
- getchar读入一个字符并将其作为int的类型返回,为了保存这个字符,必须使用赋值操作。
int ch; ch = getchar();
while (getchar() != 'n')
;
while ((ch = getchar() == ' '))
;
13.2 字符串的输入输出
字符串赋值:
char *t = "title"; char *s; s = t;
字符串上输入输出:
char ch[] = "abcdefg";
printf("%.3sn", ch); //只显示前3位
printf("%3sn", ch); //显示完
printf("%10sn", ch); //10个宽度,右对齐
printf("%-10sn", ch); //10个宽度,,左对齐
gets和scanf的区别:前者遇到空格、table或换行均会结束;后者只会在换行符结束。
char string[8];
scanf("%s", string);
scnaf("%7s",string);
printf("%.3s", string);
- scanf意味着读入一个单词,到空格、tab或回车位置。scanf是不安全的,因为不知道读入的内容的长度。
- 当输入的字符长度超过数组长度时,数组出错。所以要在canf里面规定长度如%7s。里面的数字应该比数组的大小小1。
注意点:
char buffer[100] = ""; char buffer[] = "";14.3 字符串数组
-
char **a。a是一个指针,指向另一个指针,那个指针指向一个字符串。
-
char *a[]: 是一个数组,里面元素存放的是指针,每一个指针指定了一个字符串
-
char *a[] = { "hello", "world", "!" };
-
程序参数:
int main(int argc, int const *argv[])
{
}
int main(int argc, int const **argv)
{
}
// argv[0]是命令本身,后面是命令后的字符串
int main(int argc, char const *argv[])
{
for (int i = 0; i < argc; i++) {
printf("%d: %sn", i, argv[i]);
}
return 0;
}
13.4 访问字符串的字符
- 用指针访问更方便
int count_spaces(const char s[])
{
int count = 0;
for (int i = 0; s[i] != ' '; i++)
{
count++;
}
return count;
}
int count_spaces(const char *s)
{
int count = 0;
for (;*s != ' '; s++)
{
if (*s == ' ')
count++;
}
return count;
}
- 在第二个例子中,const并没有阻止count_spaces的修改,而是阻止函数对s指向的字符的修改。因为s是传递给count_spaces的指针的副本,自增对原始指针不会有影响。
先看一些错误范例:
- 把数组名用作=的左操作数是违法的,使用=初始化字符串数组是合法的
char str1[10], str2[10]; str2 = str1; str2 = str1 = "abc"; if (str1 == str2);
使用字符串函数时,应该在前面包含**#include
strcpy函数:
char *strcpy(char *restrict *s1, const char *s2);
strcpy(str2, "abcd"); strcpy(str2, str1);
常用套路:
char *dst = (char *)malloc(strlen(src) + 1); //分配空间,不要忘了+1 strcpy(dst, src);
while (src[idx] != ' '){}
while (src[idx]){}
strlen函数:返回字符串长度,不包含末尾的‘ ’。
strcat函数:拼接两个函数,并且返回拼接后的指针。
strcpy(str1, "abc"); strcat(str1, "def"); //str1 = abcdef strcat(str2, str1); strcat(str1, strcat(st2, "ghi")) //利用返回值进行计算
strcmp函数:比较两个字符串的大小,根据两个字符串的大小返回一个大于、小于或等于0的值
int strcmp(const char *s1, const char *s2);
s1小于s2的情况:
- 前i个字符相同,第i+1个小于s2;
- s1短于s2;
用来把预处理指令替换成相应的代码,把宏替换,把注释去掉
14.2 预处理指令- 都以#开头,并不需要在行首,前面有空格就行
- 都已换行符结束,除非有标明没结束
- 符号间可以插入空格或制表符。如 # define N 100
- 注释和指令可以放同一行
- #pragma para: 对编译器进行参数化设置,其中para为参数。如进行字节对齐等。
#include:是一个预处理指令,和宏一样,编译之前就处理了。把文件的全部文本内容原封不动地插入到它所在的地方。
14.3 宏用#定义的语句,不需要用分号结束,因为这些句子不是C的句子。
#define PI 3.14159 //无分号
#define FORMAT "%fn" //完全的文本替换也是可行的
printf(FORMAT, 2*PI);
#define PRT printf("%fn", 2 * PI);
printf("%fn", 2 * PI) //这里没有分号
-
如果一个宏超过一行,那么最后一行前的行末需要加反斜杠
-
注意,空格也会被当做宏,所以一行后不要带多余的空格
预先定义的宏:可用来检测错误或调试
__LINE__ //返回行号 __FILE__ //返回文件名 __DATE__ //返回日期(mm dd yyyy) __TIME__ //返回时间(hh:mm:ss) __STDC__ //如果编译器符合C标准,则返回1 __func__ //返回所在函数名
带参数的宏
#define cube(x) ((x)*(x)*(x))
printf("%dn",cube(5));
#define MIN(a, b) ((a)>(b)?(b):(a))
编写原则:
- 整个值要有一个括号;
- 参数出现的地方要用括号。
反例:
#define cube(x) (x*x*x) cube(3+2) == (3+2*3+2*3+2) //运算不对14.4 条件编译
- #if语句
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endif
#includeint main(){ #if _WIN32 printf("This is Windows!n"); #else printf("Unknown platform!n"); #endif #if __linux__ printf("This is Linux!n"); #endif return 0; }
- #ifdef (#ifndef同理)
#ifdef 宏名
程序段1
#else
程序段2
#endif
#include#include int main(){ #ifdef _DEBUG printf("正在使用 Debug 模式编译程序...n"); #else printf("正在使用 Release 模式编译程序...n"); #endif system("pause"); return 0; }
- VS/VC 有两种编译模式,Debug 和 Release。在学习过程中,我们通常使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行很多优化,提高程序运行效率,删除冗余信息。
- 新建一个C项目,而不是新建文件
- 头文件:把**函数原型(不是函数定义)**放到一个头文件(.h)中,在需要调用这个函数的源代码文件(.c)中#include即可。所有需要用到这个函数的地方#include(包括函数定义的文件)。一般只有声明可以放在头文件中,否则容易出现重复声明。
#include//<>让编译器只在指定的目录找 #include "max.h"
- 一般做法是任何.c都有同名的.h,把所有对外公开的函数的原型和全局变量都放进去。全局变量也可以在多个文件中共享。
- 函数前面加static就使得它成为只能在所在的编译单元被使用的函数
- 全局变量前面加上static就使得它成为只能在所在的编译单元被使用的全局变量
#include#include "max.h" int main() { int a = 5; int b = 6; printtf("%fn", max(a, gAll)); return 0; }
#ifndef _MAX_H_ #define _MAX_H_ double max(doublea, double b); extern int gAll; //告诉编译器,在某个地方有个gALL #endif
#include "max.h"
int gAll = 12;
double max(double a, double b)
{
return a>b?a:b
}
变量的声明和定义
extern int i; //变量的声明 int i; //变量的定义
在max.h中,下面的语句是检查是否重定义的,建议所有的.h都包含这三句
#ifndef _MAX_H_ #define _MAX_H_ ... #endif第16章 结构、联合和枚举
- 结构:可能具有不同类型的成员的集合;
- 联合:和结构类似,成员共享同一存储空间。但每次只能存储一个成员;
- 枚举:一种整数类型。
结构-成员:在其他语言种,成为记录-字段(record - field)
-
结构声明的3种形式
struct point{ //point表示结构标记 int x; int y; }; //注意这里的分号 struct point p1,p2; //p1,p2宝表示结构变量struct { int x; int y; } p1, p2; //注意这里的分号struct point{ //第3种更常见 int x; int y; } p1, p2; //注意这里的分号 -
结构的成员在内存种是按顺序存储的
-
每个结构代表一个新的作用域,任何声明在此作用域的名字都不会和程序中的其他名字冲突。即每个结构为它的成员设置了独立的名字空间。
struct { int number; char name [NAME_LEN+1]; int on_hand; } part1, part2; struct { char name [NAME_LEN+1]; int number; char sex; } employee1, employee2;在上述两个结构中,part1和part2的成员number和name不会和employee1和employee2的成员number和name冲突。
-
结构的初始化
struct { int number; char name [NAME_LEN+1]; int on_hand; } part1 = {528, "disk drive", 10}, part2 = {914, "printer cable", 5};{ .number = 528, .name = "disk drive", .on_hand = 10 } //可以不按顺序 -
结构的操作
与数组通过下标操作相似,结构通过成员名来操作。
printf("Part name: %sn", part1.name); part1.number = 566; part1.on_hand++; //结构的成员为左值 scanf("%d", &part1.on_hand); //.号的优先级比&高 part2 = part1; //把part1的所有成员对应赋值给part2 part2 == part1; //错误操作,不能这么比较! -
结构类型的定义
typedef int number; typedef struct { int number; char name[NAME_LEN+1]; int on_hand; } Part; -
一般来说,如果把一个结构当成参数来传入/传出函数,函数要生成结构中所有成员的副本。所有为了避免系统开销过大,一般使用指针作为参数
struct date { int month; int day; int year; } myday; struct date *p = &myday; (*p).month = 12; p->month = 12;#include
struct point { int x; int y; }; struct point* getStruct(struct point*); void output(struct point); void print(const struct point *p); int main(int argc, char const *argv[]) { struct point y = {0, 0}; getStruct(&y); //得到一个指向结构的指针 output(y); output(*getStruct(&y)); print(getStruct(&y)); getStruct(&y)->x = 0; //(*p).x = 0 *getStruct(&y) = (struct point){1,2}; //结构初始化 return 0; } struct point* getStruct(struct point *p) { scanf("%d", &p->x); scanf("%d", &p->y); printf("getStruct: %d, %dn", p->x, p->y); return p; } void output(struct point p) { printf("output: %d, %dn", p.x, p.y); } void print(const struct point *p) { printf("print: %d, %dn", p->x, p->y); } struct rectangle r, *rp; rp = &r; r.pt1.x; (r.pt1).x; r->pt1.x; (r->pt1).x; //不能用rp->pt1->x,因为pt1不是指针
-
结构的嵌套
struct person_name { char first[FIRST_NAME_LEN+1]; char middle_initial; char last[LAST_NAME_LEN+1]; }; struct student { struct person_name name; int_id, age; char sex; } student1, student2; strcpy(student.name.first, "Fred"); -
结构数组:其元素为结构的数组,可构建简单的数据库。
格式:
struct part inventory[100]; print_part(inventory[i]); //访问存储在位置i的零件 inventory[i].number = 833; //赋值写法 inventory[i].number[0] = ' '; // 名字变为空字符串16.2 联合
和结构类似,其成员可以拥有不同的类型。但编译器只为其最大的成员分配足够的内存空间。联合的成员彼此覆盖。例如下面的联合中,对i的改写会改变d,对d的改写会改变i。用途举例:比如一个选礼物的场景,每次只能选一个礼物。
union {
int i;
double d;
} u;
struct {
int i;
double d;
} s;
16.3 枚举
来源:一般来说,常量应该符号化,即1、2这些数字应该有具体的名字,以便阅读方便。所以解决办法有
cons int s=1; //法1 #define CLUBS 0 //法2 #define DIAMONDS 1 #define HEARTS 2 #define SPADES 3
但以上两种方法都不够好,所以催生了枚举类型(enumeration type)
enum suit {CLUBS, DIAMONDS, HEARTS, SPADES}; //默认第一个为的值为0,后面的加1
enum suit {CLUBS = 1, DIAMONDS = 100, HEARTS = 100, SPADES}; //可以自己修改里面的值
第17章 指针的高级应用
17.5 链表
数组:在内存上是连续的区域。具有随机访问性。增加/删除元素比较麻烦
链表:往往不是连续的区域。由于必须由上一节点找出下一节点,所以不具有随机访问性。可方便插入和删除
- 声明节点结构
struct node { //node为结构标记
int value;
struct node *next;
};
struct node *first = NULL; //链表初始为空
创建节点
- 为节点分配内存;
- 把数据存到节点中;
- 把节点插到链表中。
struct node *new_node; //临时变量指向要创建的节点 new_node = malloc(sizeof(struct node)); //为新节点分配内存空间 (*new_node).value = 10; //把数据存到新节点中。注意圆括号 new_node->value = 10; //上一行的替代写法
插入节点
struct node *first = NULL;//链表初始为空 new_node = malloc(sizeof(struct node));//为新节点分配内存空间 nex_node->node = first; //先修改新节点的指针next,使其成为first指针NULL first = new_node; //使first指向新节点
以上步骤可以写成一个函数
struct node *add_to_list(struct node *list, int n)
{
struct node *new_node;
new_node = malloc(sizeof(struct node));
if (new_node == NULL) {
printf("Error: malloc failed to add to listn");
exit(EXIT_FAILURE);
}
new_node->value = n;
new_node->next = list;
return new_node; //没有修改指针,而是返回指向新产生的节点的指针
}
搜索链表:
for (p = first; p = != NULL; p = -> next)
{
...
}
惯用法:
struct node *search_list(struct node *list, int n) //搜索值为n的链表
{
struct node *p;
for (p = first; p != NULL; p = -> next)
{
if (p -> value == n)
return p; // 找到了就返回指针
return NULL; //没找到返回NULL
}
}
struct node *search_list(struct node *list, int n)
{
for (; list != NULL; list = list-> next) //除去p,用list来跟踪节点
if (p -> value == n)
return list;
return NULL;
}
struct node *search_list(struct node *list, int n)
{
for (; list != NULL && list->value != n; list = list-> next)
;
return list;
}
删除节点
- 定位要删除的节点
- 改变前一个节点,使它“绕过”删除节点
- 调用free函数回收删除节点占用的内存空间
每个函数在编译链接后总是占用一段连续的内存区,函数名就是该函数所占内存区的入口
格式:类型说明符 (*指针名)()
#include第18章 声明 第19章 程序设计 第20章 底层程序设计int add(int a, int b); int sub(int a, int b); void result(int (*pf)(), int a, int b); int main() { int a, b; int (*pf)(); //定义一个指向函数的指针 printf("input two integer: "); scanf("%d %d", &a, &b); pf = add; result(pf, a, b); pf = sub; result(pf, a, b); return 0; } int add(int a, int b) { return a+b; } int sub(int a, int b) { return a-b; } void result(int (*p)(), int a, int b)//这里的指针p没必要和pf相同 { int value; value = (*p)(a, b); //这里的指针p没必要和pf相同 printf("%dn", value); }
- 栈区:形参、局部变量、返回值等
- 有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。
- 所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。
struct 位域结构名
{
type member_name : width;
};
| 元素 | 描述 |
|---|---|
| type | 只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。 |
| member_name | 位域的名称。 |
| width | 位域中位的数量。宽度必须小于或等于指定类型的位宽度。 |
int main(){
struct bs{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%dn",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%dn",pbit->a,pbit->b,pbit->c);
}
第21章 标准库
第22章 输入/输出
22.1 三字母词
以??后面加一个字符的形式,在打印时会显示成另外的样子。MISRA-C里面规定不能使用三字母词
| 三字母词 | 输出 | 三字母词 | 输出 | 三字母词 | 输出 |
|---|---|---|---|---|---|
| ??= | # | ??( | [ | ??) | ] |
| ??< | { | ??> | { | ??/ | } |
| ??! | | | ??’ | ^ | ??- | ~ |
| 符号 | 含义 | 符号 | 含义 |
|---|---|---|---|
| & | 按位与 | | | 按位或 |
| ~ | 按位取反 | ^ | 按位异或 |
| << | 左移 | >> | 右移 |
-
按位与:让某一位或某些位为0,如用FE(11111110可以使最后一位为零);取其中一个数的一段,用FF
-
按位或:让某一位或某些位为1;使两个拼接,如0x00FF | 0xFF00
-
使用这些符号后,所得是int型。如果打印,需要用%d等
-
按位异或:x^y^y = x,即同一个变量用同一个值异或两次,等于本身。可用来加密
-
左移:左移后右边填入零,相当于乘2。所有小于int的类型,移位以int的方式来做,结果是int。
-
右移:相当于除以2。所有小于int的类型,移位以int的方式来做,结果是int。对于unsigned类型,左边填入0;对于signed类型,左边填入原来的最高位(保持符号不变)。
-
举例:
-
SID = (bit)(i_data & 0x80) i_data &= 0xf0; //取出字节的高4位 i_data <<= 4; //左移4位,即选出了字节的低4位
int main()
{
char c;
do
{
printf("Uppercase character please: ");
scanf("%c", &c);
} while (c < 'A' || c > 'Z');
printf("%cn", c | 0x20);
//printf("%cn", c & 0xDF);
return 0;
}
void swap(int *a, int *b)
{
int *a, *b;
*a = *a ^ *b;
*b = *a ^ *b;
*a = *a ^ *b;
}
swap(&x, &y);
第23章 库对数值和字符数据的支持
第24章 错误处理
- segmentation fault: 内存段冲突
void foo(void)
int main(int argc, char *argv[])
{
foo();
}
void foo(void)
{
foo();
}



