theme: orange
highlight: a11y-dark
前言
- 本文主要阅读《C Programing:A Modern Approch》2th ,中的一些章节,做一些模型结构笔记,便于参考。
- 本文对内容结构有所合并表述。Github仓库中按章节有更具体的片段代码实践,按需使用。
- 辅助参阅资料包括但不限于如GUN的C文档,和其他网络文档片段。
一、基本内容
1.简单程序一般内容
一个简单的C程序包含:指令,函数,语句,显示,注释
/*
* 指令:#include "stdio.h"
* 函数:int main(){return 0;}
* 语句:printf("this is a statements\n");
* 显示:printf("xxx content");
* 注释:如本段
* */
#include <stdio.h>
#define PI 3.1415926
int main() {
printf("this is a C program");
return 0;
}
2.编译运行程序
预处理->编译->链接
gcc/cc -o <outputfile> <sourcefile> [-w]
预处理:程序交给预处理器,执行
#
开头的指令
编译:编译器compiler(如:gcc、cc)将程序翻译成机器指令(目标代码)
链接:linker把产生的目标代码和其他附加代码(如一些库函数printf)整合,产生可执行程序
3.变量和赋值
概念 | 语法表述 |
---|---|
类型 | short/int/long/float/double/char 等 |
声明 | int var_name; |
赋值 | var_name=8; |
显示变量 | printf("var_name:%d",var_name); |
初始化 | int h=12,w=13,l=15; |
显示表达式的值 | printf("%d",h+w+l); |
读入输入 | scanf("%d",&i); |
定义常量名字 | #define INC_PER_POUND 16 或 #define REC_PI (1.0f / 3.14159f) |
二、格式化输入输出
1. printf函数和scanf函数
概念 | 语法 | 额外点 |
---|---|---|
printf() | printf(string,expr1,expr2,...); | string可能包含普通字符 和转换说明 以% 开头来表示填充值的占位符 |
scanf() | scanf工作法scanf("%d%d%f%f",&i,&j,&x,&y); | 寻找起始位置时,会忽略空白字符(空格、水平和垂直制表符、换页符、换行符)来表示填充值的占位符 |
#include <stdio.h>
int main(){
char sign = 'A';
int age = 18;
printf("class is-> %c,age is->%d,next age is->%d\n", sign, age, age + 1);
int a,b;
printf("input int number a+b:\n");
scanf("%d+%d",&a,&b);
printf("scanf value is a=%d b=%d sum=%d\n",a,b,a+b);
}
2. 转换说明符(Conversion specification)
%[flag][width][.precision]type
flag:+
右对齐 -
左对齐
width:最小宽度
precision:精度
常见转换说明符
转换符 | 含义 | 说明 |
---|---|---|
%c | 单字符 Single character | |
%d | 有符号十进制 Signed decimal integer (int) | |
%i | 有符号十进制 Signed decimal integer (int) | 用于printf时和%d 表现一致用于scanf时候会有区别 |
%u | 无符号十进制 Unsigned decimal integer (int) | |
%e (%E ) | 指数计数法浮点型 Signed floating-point value in E notation | |
%f | 定点十进制浮点型 Signed floating-point value (float) | |
%g(%G) | 按大小决定普通或指数计数法浮点型 Signed value in %e or %f format, whichever is shorter | 不显无意义的零"0" |
%o | 将整数输出为无符号八进制 Unsigned octal (base 8) integer (int) | |
%p | 指针类型 Pointer value | |
%s | 字符串文本 String of text | |
%x(%X) | 将整数打印为无符号十六进制 Unsigned hexadecimal (base 16) integer (int) | |
%% | 打印百分号% 字符(percent character) |
C99中%zu输出size_t型,打印
sizeof()
函数值
3. 转义序列(Escape sequence)
常见转义序列符
转义序列 | 含义 |
---|---|
\ | \ 符 |
\a | 警报符 |
\b | 回退符,可插曲前一单位的内容 |
\f | 换页 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
三、表达式
算术运算符
最高优先级:+ -(作为一元运算符)
* / %
最低优先级:+ -
- 一元运算右结合,二元运算左结合
类型 | 包含内容 | 结合性 |
---|---|---|
一元运算符 | + 正号运算符- 负号运算符 | 右 |
二元运算符 | + 加- 减* 乘/ 除% 余 | 左 |
赋值运算
简单赋值和复合赋值
int android, java, python,c;
/*简单赋值*/
android = 1;
java = android;
c = android + java * 10;
/*复合赋值*/
c = android = python = java * 10;
优先级(1最高) | 类型名称 | 符号 | 结合性 |
---|---|---|---|
1 | 后缀自增 | value++ | 左 |
1 | 后缀自减 | value-- | 左 |
2 | 前缀自增 | ++ value | 右 |
2 | 前缀自减 | -- value | 右 |
2 | 一元正号 | + | 右 |
2 | 一元负号 | - | 右 |
3 | 乘法类 | * ,/ ,% | 左 |
4 | 加法类 | + ,- | 左 |
5 | 赋值 | = ,*= ,\= ,%= ,+= ,-= | 右 |
下列表达式:
a = b += c++ -d + --e / -f
相当于:
step1: a = b += (c++) -d + --e / -f
step2: a = b += (c++) -d + (--e) / (-f)
step3: a = b += (c++) -d + ((--e) / (-f))
step4: a = b += (((c++) -d) + ((--e) / (-f)))
step5: a = (b += (((c++) -d) + ((--e) / (-f))))
step6: (a = (b += (((c++) -d) + ((--e) / (-f)))))
四、选择语句
逻辑表达式
- 关系运算符:>,<,>=,<=
- 判等运算符:==,!=
- 逻辑运算符:!(一元),&&,||(二元)
if
if(express) statement
if(expr) stat else stat
if(expr) stat else if(expr) stat else stat
swtich
switch (expr){
case constant-expr:statements
...
case constant-expr:statements
default:statements
}
五、循环loops
名称 | 语法 | 额外补充 |
---|---|---|
while | while (expr) stat | 惯用:while(1)无限循环 |
do while | do stat while (expr); | |
for | for(expr1:expr2:expr3) stat | 无限循环:for(;😉 |
break | break; | 跳出循环 |
continue | continue; | 控制在循环内 |
goto | identifier:stat goto identifier; |
举例:
#include <stdio.h>
int main() {
/**
* 无线循环while(1)
*/
long long int unLimit = 0;
while (1) {
printf("loop while(1) unLimit %lld\n", unLimit++);
if (unLimit > 99) {
goto Label_While_Break;
}
}
Label_While_Break:
printf("this is goto mark while(1) Label\n");
/**
* 无限循环for(;;)
*/
for(;;){
printf("loop for(;;) unLimit %lld\n", unLimit++);
if (unLimit > 200) {
goto Label_For_Break;
}
}
Label_For_Break:
printf("this is goto mark for(;;) Label\n");
for(unLimit;unLimit>0;unLimit--){
printf("this is for decrease, value = %lld\n",unLimit);
}
while (unLimit<10){
printf("this is while(unLimit<10) loop, value = %lld\n",unLimit);
unLimit++;
}
return 0;
}
六、基本类型
数值类型
整数数值类型 | 表述 | 一般读写格式 |
---|---|---|
unsigned short int 简写 unsigned short | 无符号短整型 | %hu |
short int 简写 short | 短整型 | %hd |
unsigned int | 无符号整型 | %u |
int | 整型 | %d |
unsigned long int 简写 unsigned long | 无符号长整型 | %lu |
long int 简写 long | 长整型 | %ld |
[unsigned] long long 简写 [unsigned] long long | 长长整型C99 | %llu或%lld |
浮点数值类型 | 表述 | 一般读写格式 |
---|---|---|
float | 单精度 | %f , %g , %e |
double | 双精度 | %f , %g , %e 前加小l |
long double | 扩展浮点数 | %f , %g , %e 前加大L |
短整数:前面加上h,如h<d\o\u\x>
长整数:前面加上l,如l<d\o\u\x>
short short_v = 2;
unsigned short int u_short_v = 2;
int int_v = 10;
unsigned int u_int_v =19;
long long_v = 1000;
unsigned long u_long_v=1009;
long long long_long_v = 10000;
unsigned long long u_long_long_v = 10009;
float float_v = 1.3f;
double double_v = 0.2345678901;
long double long_double_v = 0.2345678901;
printf("short=%hd\t sizeof(short)=%zu\n", short_v, sizeof(short));
printf("unsigned short=%hu\t sizeof(unsigned short)=%zu\n", u_short_v, sizeof(unsigned short));
printf("int=%d\t sizeof(int)=%zu\n", int_v, sizeof(int));
printf("unsigned int=%u\t sizeof(unsigned int)=%zu\n", u_int_v, sizeof(unsigned int));
printf("long=%ld\t sizeof(long)=%zu\n", long_v, sizeof(long));
printf("unsigned long=%lu\t sizeof(unsigned long)=%zu\n", u_long_v, sizeof(unsigned long));
printf("long long=%lld\t sizeof(long long)=%zu\n", long_long_v, sizeof(long long));
printf("unsigned long long=%llu\t sizeof(unsigned long long)=%zu\n", u_long_long_v, sizeof(unsigned long long));
printf("float(m.p=0.2) =%0.2f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=1.2) =%1.2f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=.6) =%.6f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=.10) =%.10f\t sizeof(float)=%zu\n", float_v, sizeof(float));
printf("float(m.p=.10) =%.10lf\t sizeof(double)=%zu\n", double_v, sizeof(double));
printf("float(m.p=.6) =%.6lf\t sizeof(double)=%zu\n", double_v, sizeof(double));
printf("float(m.p=.6) =%.6Lf\t sizeof(double)=%zu\n", long_double_v, sizeof(long double));
在我的电脑测试输出为:
short=2 sizeof(short)=2
unsigned short=2 sizeof(unsigned short)=2
int=10 sizeof(int)=4
unsigned int=19 sizeof(unsigned int)=4
long=1000 sizeof(long)=4
unsigned long=1009 sizeof(unsigned long)=4
long long=10000 sizeof(long long)=8
unsigned long long=10009 sizeof(unsigned long long)=8
float(m.p=0.2) =1.30 sizeof(float)=4
float(m.p=1.2) =1.30 sizeof(float)=4
float(m.p=.6) =1.300000 sizeof(float)=4
float(m.p=.10) =1.2999999523 sizeof(float)=4
float(m.p=.10) =0.2345678901 sizeof(double)=8
float(m.p=.6) =0.234568 sizeof(double)=8
float(m.p=.6) =0.000000 sizeof(double)=16
字符类型
浮点数值类型 | 表述 | 读写方式1 | 读写方式2 |
---|---|---|---|
[unsigned] char | 不同机器 可能有不同字符集 常用ASCII | 输入:scanf(“%c”,&ch); | putchar(ch)写入ch中 |
惯用法
/*skips rest of line*/
while(getchar()!='\n')
;
/*skip blanks*/
while (getchar() == ' ')
;
转大小写
/**方式一:*/
if(‘a’ <= ch && ch <= 'z')
ch =ch-'a'+'A'
/**方式二:*/
#include <ctype.h>
if(toupper(ch)='A')
实验程序:
#include <stdio.h>
#include <ctype.h>
int main() {
/*
* 2.字符类型char
* */
char char_value = 'a';
printf("char value=%c\n", char_value);
/**
* 3.转大写
*/
if ('a' <= char_value && char_value <= 'z') {
printf("upper char method normal:%c\n", char_value - 'a' + 'A');
printf("upper char method(toupper) 2:%c\n", toupper(char_value));
}
/**
* 4.typedef 优点,易理解,修改
*/
typedef int STATUS;
STATUS status_a = 100;
printf("tpyedef status:%d\n", status_a);
/**
* 5.读写字符scanf()/printf()
* 不会跳过空白字符
*/
char scanf_char;
printf("input a char (by scanf method):\n");
scanf("%c", &scanf_char);
printf("out you char value(by printf):%c\t\n", scanf_char);
while (getchar() != '\n');
printf("Skip rest of line...\n");
/**
* 6.读写字符
* getChar()/ 读取输入字符,一次只能单字符,不会跳过空白字符
* putChar(ch) 一次只能单字符输出
*
*/
printf("\ninput (by getchar() single char ):");
int get_char = getchar();
putchar(get_char);
printf("\n__________________\n");
printf("output (by getChar()) :%c\n", get_char);
printf("+++++++++++++++++++++\n");
printf("++++++input to output++++++\n");
while ((get_char = getchar()) != EOF) {
putchar(get_char);
}
return 0;
}
类型转换
中间的unsigned
全名为unsigned long
类型
- 如果变量类型至少和表达式类型一样“宽”,在赋值过程中转换:
char c;int i; float f;double d;
i=c; f=i; d=f;
- 强制转换:
cast expressing:(type-name)expression
sizeof运算
sizeof expressing: sizeof(type-name)
printf("size of int:%lu\n",(unsigned long) sizeof(int));
printf("size of int:%zu\n", sizeof(int));//c99 only
七、数组
#define N 10
#define M 11
int main(){
/**
* 1.一维数组
* 定义:int a[N];
* 初始化:int a[10]={1,2,3,4}
* 或者 a[10]={0}
* c99: int a[10]={[1]=9,[5]=7};指定位置初始化
* 同时存在 int a[]={1,2,9,4,[0]=5,8} 输出 {5,8,9,4}
*/
/**
* 2.多维数组
* 定义:int a[M][N];
* 初始化:int a[M][N] = {{1,2,3},{1,23,4},...};可不全列出
* C99(可指定位置初始化): int a[2][2] = { [0][1]=1,[1][1]=2};
*/
/**
* 3. 常量数组 constant arrays
* 定义:在数组前面加 const
*/
const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
** 4. c99中的变长数组VLA
* 变长长度不一定用变量来指定,任意表达式(可以包含运算):
* int a[3*i+5];
* int b[j+k];
* 变长数组主要限制是没有静态存储期限,没有初化式
*/
int vla[3*N+M];
}
实验程序:
#include <stdio.h>
#define N 10
#define M 11
/**
* 带参数宏
*/
#define SIZE(array) ((int)(sizeof(array)/ sizeof(array[0])))
int main() {
/**
* 1.一维数组
*/
unsigned int one_dimensional_array[10] = {12, 34, 1,};
/*sizeof返回类型为size_t为无符号类型,不强转可能会有警告,因为变量i为有符号整数*/
for (int i = 0; i < (int) (sizeof(one_dimensional_array) / sizeof(one_dimensional_array[0])); ++i) {
printf("index of One dimensional[%d]:%d\n", i, one_dimensional_array[i]);
}
/*
* 使用宏定义替换长度计算
*/
for (int i = 0; i < SIZE(one_dimensional_array); ++i) {
printf("\tindex of One dimensional[%d]:%d\n", i, one_dimensional_array[i]);
}
/**
* 8.2.1多维数组
*/
int multi_dimensional_array[M][N] = {{1, 2, 3}, {4, 5, 6}};
printf("multi dimensional array [%dx%d] value:\n", M, N);
for (int i = 0; i < M; ++i) {
printf("\n");
for (int j = 0; j < N; ++j) {
printf("index[%d][%d] = %d ", i, j, multi_dimensional_array[i][j]);
}
}
/**
* 8.2.2 常量数组 constant arrays
* 定义:在数组前面加 const
*/
const char hex_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
for (int i = 0; i < SIZE(hex_chars); ++i) {
printf("\n constant hex char [%d] = %c", i, hex_chars[i]);
}
/**
** 8.3 c99中的变长数组VLA
* 变长长度不一定用变量来指定,任意表达式(可以包含运算):
* int a[3*i+5];
* 变长数组主要限制是没有静态存储期限,没有初化式
*/
int vla[3 * N + M];
for (int i = 0; i < SIZE(vla); ++i) {
printf("\t VLA[%d]:%d\n", i, vla[i]);
}
return 0;
}
八、函数
定义语法
return-type function-name (parameters){
declarations
statements
}
声明语法
函数声明形参名也可省略:
如 int sum(int a[],int size);
可变为:
int sum(int [],int );
return-type function-name (parameters)
实际参数
描述 | |
---|---|
值传递 | 实际参数是通过值传递:调用函数时,计算出每个实际参数的值,并且把他赋值给相应的形式参数。在执行过程中,对形式参数的改变不会影响实际参数的值。因为形式参数中包含的是实际参数值的副本。 |
转换 | C允许实际参数与形式参数类型不匹配情况下函数调用,执行实际参数的提升转换 |
数组参数 | 描述 | |
---|---|---|
数组型实际参数 | 一维不用说明数组长度,数组长度需作为参数传递进来 多维时只可不说明第一维长度 | int sum(int a[],int length) |
变长数组型形式参数 | 使用变长数组形式参数时,参数顺序很重要 | int sum(int n,int a[n]) |
数组参数使用static | 表明最少长度,多维时只作用第一维 | int sum(int [static 10], int) |
九、程序结构
类型 | 生命 | 作用域 |
---|---|---|
局部变量 (函数体内声明的变量) | 自动存储期限,函数调用时自动分配,返回时回收 | 块作用域: 从变量声明开始点,到函数体的末尾 |
静态局部变量 (加 static 关键字的元素) | 静态存储期限,拥有永久的存储单元。整个程序执行期间都会保留变量值 | 1.拥有块作用域 2.对其他函数不可见,同一函数再调用,保留这些数据。 |
形式参数 | 自动存储期限,在每次函数调用时自动初始化(调用中通过赋值获得相应实际参数的值) | 拥有块作用域 |
外部变量(全局变量) | 静态存储期限,外部变量的值将永久保留 | 拥有文件作用域 : 从变量声明的点开始到文件末尾 |
十、指针
指针变量
在计算机中,指针取值范围可能不同于整数取值范围,可以用指针p存储变量i的地址,也就是说指针就是地址,指针变量就是存储地址得变量
取址运算符、间接寻址运算符、指针赋值
int main(){
int i=0,*p;//声明指针变量p
p=&i;// & 取址运算符,p指向i,把i的地址赋值给了指针p
int k=1,*q=&k;//合并写法
printf("%d",*p);// *(间接寻址运算符)访问存储在对象中的内容 ,也可想象成&的逆运算
}
int main(){
int i=0,*m,*n;//声明指针变量m,n
m=&i;// i地址复制给m
n=m;// m的内容复制给n
*n=2;// 此处i的值变为2
printf("%d",*p);// *(间接寻址运算符)访问存储在对象中的内容
}
指针作为参数
重要作用
- 函数调用中用作实际参数的变量无法改变,当希望函数能够改变变量时,
指针作为参数就能解决此问题。 - 指针传参高效原因是,如果变量需要大量存储空间,传递变量的值有时会浪费时间和空间。
void decompose(double, long *, double *);
void decompose(double x, long *int_part, double *frac_part) {
*int_part = (long) x;
*frac_part = x - *int_part;
}
int main{
double pi = 3.1415926;
long int_part;
double frac_part;
// 函数用指针作为参数,改变变量的值
decompose(pi, &int_part, &frac_part);
printf("pi int_part=%lu, frac_part=%f\n", int_part, frac_part);
}
const保护指针参数
有时候我们仅需要检查参数的值,而不想改变他的值也有可能。const
关键字保护参数,如void fruit(const int *price)
指针作为返回值
/**
* 指针作为返回值
* @return
*/
int *find_middle(int n, int a[n]) {
return &a[n / 2];
}
int *find_max(int *a, int *b) {
if (*a > *b) {
return a;
} else {
return b;
}
}
十一、指针和数组
1.指针算术运算
- 加,减,
- 指针相减为距离
- 指针比较取决于在数组中的位置
- 复合常量指针(不用先申明一个数组变量)
int main() {
int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10};
int *p = &a[0], *q = &a[6];
//1. 加整数
p += 4;
printf("p %d\n", *p);
//2. 减整数
p -= 3;
printf("p %d\n", *p);
//3. 指针相减
printf("q-p = %lld\n", q - p);
//4. 指针比较(取决于在数组中的位置)
printf("q>p:%d q<p:%d\n", q > p, q < p);
//5. 复合常量的指针
int *k = (int[]) {2, 4, 6, 8, 10};
k += 4;
printf("*K=%d\n", *k);
return 0;
}
2.指针用于数组处理
#define N 10
int main() {
// Q&A 有些编译器来说,实际上依靠下标的循环产生更好的代码
int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, *p;
for (p = &a[0]; p < &a[N]; p++) {
printf("%d\n", *p);
}
return 0;
}
表达式 | 含义 |
---|---|
*p++或*(p++) | 自增前表达式的值是*p,以后再自增p |
*§++ | 自增前表达式的值是*p,以后再自增*p |
++p或(++p) | 先自增p,自增后的表达式的值是*p |
++*p或++(*p) | 先自增p,自增后的表达式的值是p |
3.数组名作为指针
用数组名作为指针
可以使数组遍历变得更加简洁,核心体现在下面代码a+N部分
#define N 10
int main() {
// 数组名作为指针
int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, *p,*q;
// 原始写法
for (q = &a[0]; q < &a[N]; q++) {
printf("*q=%d ", *q);
}
int sum = 0;
// 惯用法(更简洁)
for (p = a; p < a + N; p++) {//此处a+N理解为,将a当作指针,然后做了指针的加法运算
sum += *p;
printf("\n sum = %d *p=%d", sum,*p);
}
return 0;
}
用指针作为数组名
#define N 10
int main() {
// 用指针作为数组名
int a[] = {1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, *p = a, *q = &a[0], *k;
k = a;
// 三种效果相同
for (int i = 0; i < N; ++i) {
printf("p[%d]=%d\n", i, p[i]);
printf("q[%d]=%d\n", i, q[i]);
printf("k[%d]=%d\n", i, k[i]);
}
return 0;
}
4、指针和多维数组
数组指针和指针数组
int (*p)[n];//数组指针
int *p[n];//指针数组
名词理解:
方便区分中间加上“的”字
数组的指针:是一个指针,什么样的指针呢?指向数组的指针。
指针的数组:是一个数组,什么样的数组呢?装着指针的数组。
理解方式:
形容词+主要名词
step1:由优先级
()
>[]
>*
确定主要名词step2: 由剩余部分确定形容词
二维数组的行列
#include <stdio.h>
#define N 10
#define M 10
int main() {
// 用指针作为数组名
int a[M][N] = {{1, 2, 3, 5, 6, 7, 8, 9, 10, 11},};
printf("eg1:使用指针打印数组 \n");
int *p;
// 1.按在存储上的特点,当成一维数组(Q&A:合法但是可读性差,在现代编译器上的速度优势极少甚至没有)
for (p = &a[0][0]; p < &a[M - 1][N - 1]; p++) {
printf("%d ", *p);
}
printf("\neg2:使用指针更改行\n");
// 2.使用指针更改第m行
int *row, m = 2;
for (row = a[m]; row < a[m] + N; row++) {
*row = 20;
}
/* 数组指针vs指针数组
* Tips:
* 优先级:()>[]> *
* 数组的指针:int (*y)[N]为数组指针,可理解为 int(*y)[N]是个含有N个元素的数组类型指针
* 帮助记忆:step1.一个普通指针 int *y ,根据优先级,(*p)是一个指针,具体是个xxx的指针
* step2.一个数组类型指针需要后面加上[],变为数组类型指针,因为优先级关系,需要将用括号()将 *y 包裹起来,变为int (*y)[]
* step3.在step2上没办法确定数组类型指针以此走多少步,所以在[]上加入步长N,最终为 int (*y)[N]
*
* 指针的数组:int *y[N]为指针数组,可理解为 N个指针元素的数组
* 帮助记忆:step1.根据优先级,p[]是一个数组,是个xxx的数组
*/
// 3.使用指针更改第n列
printf("\neg3:使用指针更改第n列\n");
int (*y)[N], n = 3;
for (y = &a[0]; y < &a[M]; y++) {
(*y)[n] = 30;
}
// 4.常见嵌套遍历
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
printf("a[%d][%d]=%d ", i, j, a[i][j]);
}
printf("\n");
}
return 0;
}
十二、字符串
1.字符串字面常量
C语言如何存储字符串
c语言将字符字面常量当做字符数组来处理,长度为n的字符串,系统会分配长度为n+1的内存空间
字符串变量
声明变量并初始化
char date1[8]="June 14";
与 char date2[9]="June 17";
与char date3[7]="June 19";
字符数组与字符指针
int main() {
//惯用法,由于末尾空字符
#define STR_LENGTH 8
char name[STR_LENGTH + 1] = "abc";
char *p_str = "hello world!";
char c_array[] = {'a', 'b', 'c'};
char c_array2[] = "abc2";
char *p;
p=c_array;//p作为字符串之前,必须把p指向字符数组
int size_name = (sizeof(name) / sizeof(name[0]));
for (int i = 0; i < size_name; ++i) {
printf("name# %c total=%d\n", name[i], size_name);
}
int i = 0;
while (((int) *p_str) != '\0') {
printf("p_str#%d %c\n", i++, *(p_str++));
}
printf("p_str#end %c\n", *p_str);
return 0;
}
2.字符串读写
printf()与puts()写(打印)函数
函数 | 表现 |
---|---|
puts() | 写完后自动添加额外的换行符,从而移动到下一行开始处 |
printf() | 1.逐一写字符串中的字符,直到空字符停止 |
scanf()与gets()读(输入)函数
函数 | 表现 |
---|---|
gets() | 1.可以读入一阵行 2.不会在开始读字符串前跳过空白字符 3.持续读入到换行符停止,此外会忽略换行符,不会存储到数组用,用空字符代替换行符 |
scanf() | 1.读入字符串永远不包含空白字符 2.换行符、空格、制表符会使它停止读入,因此通常读不了一整行 |
#include <stdio.h>
#include <string.h>
#define NEXT "(y/n)"
#define YES "y"
#define NO "n"
int main() {
char str[] = "we have fun yet!";
char *str2 = "we have fun2 yet! 酷";
char str3[3] = "abc";
//1.字符串写
printf("%.6s\n", str);
printf("%s\n", str2);
printf("%s\n", str3);
//2.字符串读
char str_a[3] = "abc";
/*
*使用scanf()方式
*/
while (1) {
printf("input by scanf():\n");
scanf("%s", str_a);
puts(str_a);
int size_s_str = (sizeof(str_a) / sizeof(str_a[0]));
printf("s_a size=%d \n", size_s_str);//本机测试输入超过3任然能显示(内存越界可能产生莫名其妙的结果)
printf("重新输入%s?:\n", NEXT);
scanf("%s", str_a);
if (strcmp(str_a, NO) == 0) {
break;
}
}
getchar();
/*
*使用gets()方式
*/
while (1) {
printf("input by gets():\n");
gets(str_a);
puts(str_a);
int size_s_str = (sizeof(str_a) / sizeof(str_a[0]));
printf("s_a size=%d \n", size_s_str);//本机测试输入超过3任然能显示(内存越界可能产生莫名其妙的结果)
printf("重新输入%s?:\n", NEXT);
gets(str_a);
if (strcmp(str_a, NO) == 0) {
break;
}
}
return 0;
}
逐字符读入字符串&自定义read_line()
考虑的问题:
- 存储前是否该跳过空白字符
- 什么字符导致函数停止读取,存储还是忽略这类字符
- 字串太长无法存储,怎么处理剩余字符,丢弃还留给下一次输入
举例:定义一个不跳过空白,在换行符停止(不存储),忽略额外字符的read_line()
函数
int read_line(char[], int);
int read_line(char str[], int n) {
int ch, i = 0;
while ((ch = getchar()) != '\n') {
if (i < n) {
str[i++] = ch;
}
}
str[i] = '\0'; //手动加字符结尾
return i;
}
int main() {
char str[] = "we have fun yet!";
int length = read_line(str, 4);
puts(str);
printf("length=%d",length);
return 0;
}
举例2:从标准输入中读取字符,直到遇到换行符 '\n'
为止,函数返回为输入的字符内容
typedef char *String;
String read_line() {
String s1 = NULL;
int size = 0, capacity = 1;
s1 = (String) malloc(capacity * sizeof(char)); // 分配内存
if (s1 == NULL) {
printf("错误:内存分配失败。\n");
exit(EXIT_FAILURE);
}
char c;
while ((c = getchar()) != '\n') {
s1[size++] = c;
if (size == capacity) { // 如果需要,增加容量
capacity *= 2;
s1 = (String) realloc(s1, capacity * sizeof(char));
if (s1 == NULL) {
printf("错误:内存分配失败。\n");
exit(EXIT_FAILURE);
}
}
}
s1[size] = '\0'; // 添加空字符
return s1;
}
C语言中常见字符串库
<string.h>
中有许多操作字符串的函数,下面列举少数几种
函数名 | 原型 | 描述 |
---|---|---|
strcpy | char *strcpy(char *s1,const char *s2) | 1. 复制s2到s1,直到遇见s2第一个空字符(此空字符也复制) 2. 不改变s2,函数返回s1指针 |
strlen | size_t *strlen(const char *s) | 返回数组中字符串长度 |
strcat | char *strcat(char *s1,const char *s2) | s2内容(含'\0' )追加到s1末尾,返回s1(指针) |
strncat | char *strcat(char *s1,const char *s2,int n) | 1.s2追加到是末尾,遇空字符终止 2.n为待复制字符数(不包括 '\0' 空字符) 3.更安全 |
strcmp | int *strcmp(const char *s1,const char *s2,int n) | 利用字典顺序比较 空格符<数字<大写<小写字母 |
示例程序
#include <stdio.h>
#include <string.h>
#define MAX_REMINDER_SIZE 60
#define MSG_SIZE 60
int read_line(char *, int);
int read_line(char s[], int n) {
int count = 0, ch;
while ((ch = getchar()) != '\n') {
if (count < n)
s[count++] = ch;
}
s[count] = '\0';
return count;
}
/*
*
* 月提醒列表:
* 按日期排序,输入0打印,打印格式如下
* day reminder
* 5 task1
* 9 task2
* 15 task3
* 25 task4
*/
int main() {
//1.按日期从小到大提示 day msg,其中day右对其,msg左对其
//2.超过MAX_REMINDER_SIZE,不能录入
//3.输入0时候显示所有内容
char reminders[MAX_REMINDER_SIZE][MSG_SIZE + 3];//保存
char day_str[3], remind_msg[MSG_SIZE + 1];//日期字串 消息字串
int day_number = 0, msg_count = 0;
char s1[10] = "abc", s2[10] = "xyz";
strcpy(s1, s2);//xyz
strcat(s1, s2);//xyzxyz
printf("%lld", strlen(s2));
for (;;) {
if (msg_count > MAX_REMINDER_SIZE) {
puts("+++++++++++ No space left++++++++");
break;
}
printf("Enter a day and reminder:");
scanf("%2d", &day_number);
if (day_number == 0) {
break;
}
//读入前两位数字并保存到
sprintf(day_str, "%2d", day_number);
//读取指定长度字符
read_line(remind_msg, MSG_SIZE);
//按日期小到大排
int i = 0, j = 0;
//从0行开始,找到按日期大小排的位置i
for (i = 0; i < msg_count; i++) {
if (strcmp(day_str, reminders[i]) < 0)
break;
}
// 如果i的位置是小于当前msg总数,则将此条msg插入i行,i行后面的往后挪一行
for (j = msg_count; j > i; j--) {
strcpy(reminders[j], reminders[j - 1]);//将j-1行挪到j行
}
//复制日期到行
strcpy(reminders[i], day_str);
//拼接msg到后面
strcat(reminders[i], remind_msg);
msg_count++;
}
printf("day reminder\n");
for (int i = 0; i < msg_count; ++i) {
printf(" %s\n", reminders[i]);
}
return 0;
}
字符串数组
存储字符串数组最佳的方式:
- 最明显方式:数组式,缺点比较浪费空间
- 省空间方式:指针式
/*
* 存储字符串数组最佳方式
*/
int main() {
//方式一:数组式(废空间)
char plant_a[][8] = {"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto"};
//方式二:指针式
char *pant_b = {"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto"};
return 0;
}
命令行参数
/*
* 命令行参数
* 如:ls -l count.c
* arg[0]程序名 剩余为余下命令参数,最后附加一个空指针argv[argc]=NULL
*/
int main(int argc,char* argv[]) {
for (int i = 0; i <= argc; ++i) {
printf("%d=%s\n",i,argv[i]);
}
char **p;
for (p=&argv[0];*p!=NULL;p++){
printf("%s\n",*p);
}
return 0;
}
十三、预处理器
1.预处理器原理
如
gcc <SourceFile> -E
可以看到预处理器的输出
注意: 预处理器仅知道少量C语言的规则。因此在执行指令时是有可能产生非法程序,有时看起来正常但错误找起来难,可以检查一下预处理输出是一种方式
2.预处理指令
特征:
- 指令都以#开始
- 指令的符号间可以插入任意数量空格或水平制表符
- 指令总在第一个换行符出结束,除非使用
\
符加入当前行末尾,明确地指明要延续 - 指令可以出现在程序的任何地方
- 注释可以和指令放在同一行
部分预处理指令:
预处理指令范畴 | 指令 |
---|---|
宏定义 | #define 指令定义一个宏#undef 志宁删除一个宏 |
条件编译 | #if 、#ifdef 、#ifndef 、#elif 、#else 、#endif 以测试条件来确定程序是否包含一段文本块 |
文件包含 | #include 指定的文件内容被包含到程序中 |
其他特殊指令 | 1. #error 显示出错消息2. #line 改变程序行编号方式 3. #pragam 为编译器执行某些特殊操作特供一种方法 |
3.宏定义
标题 | 描述 | 例子 |
---|---|---|
简单宏 | #define 标识符 替换列表 | |
参数宏 | #define 标识符(x1,x2,…,XN) 替换列表 | |
#运算符 | 将宏的一个参数字符串化,只允许出现在参数宏的替换列表 | |
##运算符 | 将两个记号(如标识符)粘合在一起,变成一个记号 | |
预定义宏 | 一种已经定义好的宏,每一个都是整数常量或字符串字面量 | __DATE__ ,__TIME__ ,__STDC__ ,__FILE__ ,__LINE__ |
空宏参数 | 允许宏调用时任意参数为空,主要出现在参数宏或# 运算符或## 运算符的调用中 | |
参数个数可变宏 | 在宏定义的参数列表最后中使用... ,…省略号在,__VA_ARGS__ 专用标识符,代表与... 对应的参数 |
特殊
__func__
标识符与预处理器
无关,相当于当前执行函数都的函数名字符串变量
#include <stdio.h>
/*
* 1. 简单宏
*/
#define N 10
#define D "=%d\n"
/*
* 2.参数宏
*/
#define IS_EVEN(n) ((n)%2==0)
/*
* 3. #运算符,用于参数宏的替换列表中,字符串化
*/
#define P_INT(x) printf(#x D,x)
/*
* 4. ##运算符,粘合两个记号,将两个记号变为一个记号
*/
#define M_K(n) i##n
#define JOIN(x,y,z) x##y##z
int main(){
IS_EVEN(3);
int a=3,b=2;
P_INT(a-b);
int M_K(1)=1,M_K(2)=2;// 相当于声明i1,i2
P_INT(i1-i2);
/*
* 5. 预定义宏,整数常量或字符串字面量
*/
puts(__DATE__);
puts(__TIME__);
printf("%d",__STDC__);
printf("%s %d",__FILE__,__LINE__);
/*
* 6. 空宏参数
*/
int M_K()=0;
P_INT(i);
int JOIN(r,,),JOIN(r,s,t),JOIN(r,,t);
r=1,rst=2,rt=3;
/*
* 7 参数个数可变的宏,...省略号在参数列表最后,__VA_ARGS__专用标识符,代表与...对应的参数
*
*/
#define TEST(condition,...) ((condition)? \
(printf("test passed:%s\n",#condition)): \
(printf(__VA_ARGS__)))
TEST(3>2,"3>2 %s","test");
TEST(2>3,"output %d is not big than> %d\n",2,3);
/*
* 8. __func__标识符,与预处理器无关,每函数都可访问
*/
printf("%s return",__func__ );
#undef N
return 0;
}
十四、 构建大型程序
多文件程序的文件
标题 | |
---|---|
源文件 | .c 结尾的全部文件 |
头文件 | 惯例.h 结尾的文件 |
#include 包含规则
标题 | |
---|---|
#include <文件名> | 搜寻系统头文件 |
#include "文件名" | 搜寻当前目录头文件,后系统文件目录 |
#include 记号 | 如#include CPUFILE ,CPUFILE 是一个根据不同系统下不同架构的宏定义 |
头文件中的一般其他内容
标题 | |
---|---|
共享宏定义 | #define TRUE 1 |
共享类型定义 | typedef int BOOL |
共享函数定义 | 函数原型声明 |
共享变量声明 | 变量声明和定义int i ,编译器会分配变量内存空间变量声明 extern int i ,extern 编译器不用每次编译文件时为i分配变量内存空间 |
保护头文件 | 多文件归根都引用了同一个头文件,用#ifndef 和#endif 指令封闭文件内容 |
#error 指令 | 经常放在头文件中,用来检查不包含头文件的条件 |
// 举例一个头文件包含内容
#ifndef PROGRAM_COURSE_H_
#define PROGRAM_COURSE_H_
#define TRUE 1
typedef int BOOLEAN;
int value;
extern int ext_value;
int sum(int,int);
#ifndef __STDC__
#error "This C program requires a Standard C compiler!"
#endif
#endif //PROGRAM_COURSE_H_
十五、 结构&联合&枚举
概述
类型 | 描述 |
---|---|
结构struct | 结构里的每个成员存储在不同地址中 |
联合union | 1.联合里的每个成员存储在同一地址中 2.编译器为最大成员分配足够内存空间 3.任一成员改变值会改变其他成员的值 |
枚举enum | 1. 系统内部将枚举变量和常量看做整数 2. 默认情况下按0.1.2…赋给枚举中的常量 3.可以为枚举常量自由选择值,在声明时指明数值 |
命名和用法
两种命名的方法
#include <stdio.h>
/*-----------------------结构----------------------------*/
//省略了结构标记
struct {
int version_1;
char name_1;
} a1;
//(结构)定义方式1:包含了结构标记
struct Part {
int version_1;
char name_1;
} b1, b2, b3;//同时声明3个结构体变量,也可不在此处声明
struct Part b4;//声明一变量
//(结构)定义方式2:使用typedef
typedef struct {
int version_2;
char name_2;
} Part2;//此处使用类型定义将结构命名为类型Part2
Part2 c1, c2;//声明两个变量
/*-----------------------联合----------------------------*/
//(联合)定义方式1:包含了联合标记
union Union_1 {
int weight;
double name;
} d1, d2, d3;//同时声明3个联合变量,也可不在此处声明
//(联合)定义方式2:使用typedef
typedef union {
int version_2;
char name_2;
} Union_2;//此处使用类型定义将联合命名为类型Part2,
/*-----------------------枚举----------------------------*/
enum { ONE, TWO, THR } f1;//定义3个枚举常量 和一个变量
enum Enum_1 { FIV, SIX } f2;// 方式一:包含了枚举标记
typedef enum { TRUE=1, FALSE=0 } BOOL;//方式2:使用typedef类型定义
十六、指针高级应用
动态存储分配&空判断&释放
三种内存分配函数(内存块都来至于堆) | 描述 | 原型 |
---|---|---|
malloc | 分配内存块,但不进行初始化,少一步比calloc 更高效 | void *malloc(size_t size) |
calloc | 分配内存块,并进行清零 | void *calloc(size_t nmemb,size_t size) |
realloc | 调整先前分配的内存块 | void *realloc(void *ptr,size_t size) |
内存释放函数 | 描述 | 原型 |
---|---|---|
free | 释放不需要的内存块 | void *free(void *ptr) |
空指针 | |
---|---|
NULL | 如分配内存时未找到足够大小,就会返回空指针,if(P=NULL){...} |
悬空指针 | 如在释放p指向的内存后,再访问或修改释放掉的内存块 |
其他 | |
---|---|
-> 运算符 | 称为右箭头选择,用于 node->value 替代 *node.value 的组合 |
restrict 受限指针 | int *restrict p; 1.如果p指向的对象需要修改,则对象不会允许除p之外的任何方式访问 2.如果一个对象有多种访问方式,通常将这些方式互称为别名 |
十八、 声明
声明说明符
存储类型 | 存储期限 | 作用域 | 链接 |
---|---|---|---|
auto | 自动存储期限,不明确指明,块内声明变量默认的 | 块作用域 | 无链接 |
static | 静态存储期限 | 块作用域或文件作用域 | 块内无链接,块外内部链接 |
extern | 静态存储期限 | 块作用域或文件作用域 | 不做阐述 |
register | 存储在寄存器 (CPU存储单元)自动存储期限 | 块作用域 | 无链接 |
- 存储期限:内存单元的释放和获得
- 作用域:可以引用变量的那部分程序文本
- 链接:共享变量的范围,外部链接可以被几个或全部文件共享,内部链接属于单一文件,无连接属于函数独享
类型限定符
const | 声明一些类似变量的对象,这些变量时只读的 |
volatile | 用于底层编程中,通知编译器某些数据是易变的,这种内存空间的值容易在运行期间发生改变,即使程序自身并未试图存放新值 |
restrict 受限指针(c99) | int *restrict p; 1.如果p指向的对象需要修改,则对象不会允许除p之外的任何方式访问 2.如果一个对象有多种访问方式,通常将这些方式互称为别名 |
类型说明符 | void ,char , short ,int ,long ,float ,double ,signed ,unsigned ,结构联合枚举说明符,typedef创建的类型名 |
声明符
- 包含标识符,标识符前面可能有
*
,后面可能有[]
,()
。3种符号组合可以创建复杂声明- 解释复杂声明
- 从内向外
- 选择优先级,
()
>[]
>*
- 可以用类型定义来简化复杂声明
初始化式
初始化变量
标题 | 初始化式 |
---|---|
静态存储期限的变量 | 必须是常量 |
自动存储期限变量 | 不需要是常量 |
花括号中的数组、结构、联合 | 必须只含常量表达式 |
自动类型的结构、联合 | 可以是另外一个结构和联合 |
未初始化的变量
未初始化变量有未定义的值,但并不总是这样
- 自动存储期限变量没有默认初始值。不能预测初始值,每次变量变为有效时值可能不同
- 静态存储期限变量默认情况初始值为0。
内联函数
- 额外开销:调用函数和函数返回所产生的工作量。
- 使用内联函数
inline
:减少额外开销。依赖编译器,有的编译器可以忽略这一建议。- 函数中不能定义可改变的static变量。c99
- 函数中不能引用具有内部链接的变量。c99
十九、 程序设计
- 模块
聚合性 | 低耦合高内聚 |
类型 | |
---|---|
数据池 | 一些相关变量或常量的集合,如<limits.h> |
库 | 相关函数的集合 |
抽象对象 | 对于隐藏的数据结构进行操作的函数集合 |
抽象数据类型ADT | 具体数据实现方式隐藏起来的数据类型称为抽象数据类型 |
- 信息隐藏
标题 | |
---|---|
安全性 | |
灵活性 |
- 抽象数据类型
标题 | |
---|---|
封装 | 其他语言对封装支持要好一些 |
不完整类型 | 描述了对象,但缺少定义对象大小的信息 |
- 抽象数据类型例子 —— 栈使用不同方式实现(具体见示例程序)
使用定长,变长,链表实现,但用同一个头文件
二十、 底层程序设计
- 位运算符
标题 | |
---|---|
<< | 左移 |
>> | 右移 |
~ | 按位求反 |
& | 按位求与 |
^ | 按位异或 |
| | 按位或 |
-
结构种的位域
-
其他底层技术
二十一、 标准库
标题 | |
---|---|
C89 15个 | |
<assert.h> | |
<ctype.h> | |
<errno.h> | |
<float.h> | |
<limits.h> | |
<local.h> | |
<math.h> | |
<setjmp.h> | |
<signal.h> | |
<stdarg.h> | |
<stddef.h> | |
<stdio.h> | |
<stdlib.h> | |
<string.h> | |
<time.h> | |
C99 新增9个 | |
<cxomplex.h> | |
<fenv.h> | |
<inttypes.h> | |
<iso646.h> | |
<stdbool.h> | |
<stdint.h> | |
<tgmath.h> | |
<wchar.h> | |
<wctype.h> |
二十二、 输入、输出
1. 流
标题 | |
---|---|
文件指针 | File *fp1 C中对流的访问是通过文件指针实现 |
标准流
文件指针 | 流 | 默认含义 |
stdin | 标准输入 | 键盘 |
stdout | 标准输出 | 屏幕 |
stderr | 标准错误 | 屏幕 |
重定向
重定向输入:强制程序从文件输入而不是从键盘输入 | 方法:前面加上字符< ,如demo <in.dat |
重定向输出:强制程序从文件输出而不是从屏幕输出 | 方法:前面加上字符> ,如demo >out.dat |
<stdio.h>支持的两种类型文件
文本文件 | 字节表示字符,可检查或编辑文件 |
二进制文件 | 字节不一定表示字符,字节组可以表示其他类型数据 |
2. 文件操作
打开文件 | fopen(文件名,模式) | 参考模式表 |
关闭文件 | fclose(文件指针) | 参考模式 |
打开的流附加文件 | freopen(文件名,模式,附加文件指针) | 附加文件指针一般为标准流或其他 |
临时文件 | 1.File *tempfile(void) 2. char *tempnam(char *s) | 1. tempfile,易用,缺点不能命名,按需保存起来 2. tempnam生成一个唯一的、可用于命名临时文件的字符串,你需要手动将其作为参数传递给其他函数(如 fopen())以创建实际的临时文件。请注意,在某些系统上,由于安全性问题而不推荐使用此函数。 |
文件缓冲 | 1. fflush(文件指针) 2. void setbuf(文件指针,缓冲数组,<缓冲模式>,>使用缓冲大小> ) | 1. fflush清洗文件缓冲区 2. setbuff 按大小位置缓冲类型(_IOFBF,_IOLBF,_IONBF)控制缓冲流 |
文件重命名 | rename(旧名,新名) | |
文件删除 | remove(文件名) | |
文件定位 | 1.fseek 移动到文件的某些位置2. ftell 返回当前文件位置3. rewind 把文件位置设置在起始处4. fgetpos 获取文件位置5. fsetpos 设置文件位置 |
模式 | 二进制文件 | 文本文件 |
---|---|---|
读 | rb | r |
写(文件无需存在) | wb | w |
追加(文件无需存在) | ab | a |
读和写(从文件头开始) | r+b 或 rb+ | r+ |
读和写(如果文件存在就截去) | w+b 或 wb+ | w+ |
读和写(如果文件存在就追加) | a+b 或 ab+ | a+ |
3. 输入输出
输出类型 | 函数 | 表述 |
---|---|---|
流输出 | printf() | 向标准输出stdout写入内容 |
流输出 | fprintf(File*,const char *,...) | 向任意输出流写入内容 |
字符串输出 | sprintf(char*,const char *,...) | 输出写入字符数组 |
字符串输出 | snprintf(char*,size_t,const char *,...) | 输出写入字符数组,限制长度 |
字符输出 | putchar(int) | 向标准流stdout写一个字符 |
字符输出 | fputc(int,File*) | 任意流写一个字符 |
字符输出 | putc(int,File*) | 任意流写一个字符,效果同上 |
行输出 | puts(const char *) | 向标准流stdout写字符串 |
行输出 | fputs(const char *,File *) | 向任意流写字符串 |
块输出 | fwrite(void*,size_t,size_t,File*) | 将内存中的数组复制给流,控制大小 |
输入类型 | 函数 | 表述 |
---|---|---|
流输入 | scanf() | 从标准输入stdin读入内容 |
流 | fscanf(File*,const char *,...) | 从任意输入流读入内容 |
字符串 | sscanf(char*,const char *,...) | 输入写入字符数组,通常用fgets 后,再使用sscanf 进一步处理 |
字符 | getchar(void) | 从标准流stdin读一个字符,#define getchar) getc(stdin) |
字符 | fgetc(File*) | 从任意流读一个字符 |
字符 | getc(File*) | 从任意流读一个字符,效果同上 |
字符 | ungetc(int,File*) | 从任意流读入的字符“放回”并清除流的文件末尾指示器 |
行 | gets(char *) | 从标准流stdin 读一行 |
行 | fgets(char *,int n,File *) | 从任意流读字符串,到n-1个,或换行符结束操作 |
块 | fread(void*,size_t,size_t,File*) | 从流读入到数组的元素,控制大小 |
二十三、 库对数值和字符数据的支持
包含浮点,整数大小,数学计算函数,字符处理,和字符串的处理,此处省略
二十四、 错误处理
标题 | |
---|---|
<assert.h> | 诊断错误 |
<errno.h> | 1.errno 变量表明错误2. <stdio.h> 中的perron(const char *) 函数显示出错信息3. <string.h> 中的strerror(int) 指向描述这个错误的字符串 |
<signal.h> | 在UNIX的信号讨论更丰富,使用场景更丰富 1. signal 函数2. raise 函数 |
<setjmp.h> | 函数间跳转 1. int setjmp(jmp_buf env) 标记一个位置 2. void longjmp(jum_buf env,int val) 返回标记的位置 |
errno.h
errno.h头文件定义了一个整数变量errno,它存储了最近一次系统调用执行的错误代码。当系统调用失败时,errno变量会被设置为相应的值,以指示错误类型。我们可以使用errno.h头文件提供的宏来检测和处理错误。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int c, char *a[]) {
FILE *fp;
int num;
fp = fopen("test.txt", "r");
errno = 0; //每次使用前最好重置0
if (fp == NULL) {
printf("Error opening file: %s\n", strerror(errno));
}
fclose(fp);
return 0;
}
这个程序中,尝试打开一个不存在的文件,并检查是否成功。由于文件并不存在,fopen()函数将失败,并将errno
设置为相应的错误代码。在我们的程序中,我们使用strerror()
函数将errno
转换为描述错误的字符串,并将其打印到屏幕上。
setjmp.h
#include <setjmp.h>
void callee(jmp_buf env) {
longjmp(env, 1);
/* unreachable */
}
void caller() {
jmp_buf env;
switch (setjmp(env)) {
case 0:
callee(env);
/* unreachable */
break;
case 1:
printf("returned from callee\n");
break;
default:
printf("unexpected setjump value\n");
}
}
signal.h
一个简单的示例程序,展示如何使用signal()函数来捕获和处理SIGINT信号
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
// 信号处理程序
void sig_int_handler(int signum) {
printf("Received SIGINT signal!\n");
exit(signum);
}
int main() {
// 注册SIGINT信号处理程序
signal(SIGINT, sig_int_handler);
printf("Waiting for SIGINT signal...\n");
while (1) {}
return 0;
}
这个例子中,当程序接收到SIGINT信号(通常由CTRL+C触发)时,它会执行sig_int_handler()函数并输出一条消息,然后退出程序并返回信号编号。如果不捕获SIGINT信号,则默认行为是终止进程。在程序执行过程中,我们使用一个无限循环来等待信号的到来。
二十五、 国际化
包含<local.h>、多字节字符和宽字符、双字符三字符、通用字符名,<wchar.h>扩展多字节、<wctype.h>宽字符分类和映射工具
二十六、 其他库函数
标题 | ||
---|---|---|
<stdarg.h> | 可变参数 | va_arg,va_start,va_end,va_copy宏 |
<stdlib.h> | 通用使用工具 |
总结
- 《C Programing:A Modern Approch》2th 是一本优秀的C语言学习书籍,由浅入深介绍,对读者非常友好,学后还能根据重复部分再进一步整理缩减。
- 对于图形编程,多线程,进程知识点是后面需要参照其他书籍进行补充
- 实践代码加深印象