LinMao's Blog
学习科研记录与分享!

《C语言程序设计》

书名:《C语言程序设计》(第二版)
作者:刘明军 韩玫瑰 主编
出版社:电子工业出版社
之前的C语言学的不够系统,有很多知识盲区,这两天看了看《C语言程序设计》这本书,感觉这本书挺不错的,全书主要是以C语言的数据类型为主线展开讲的,把指针结构体这些以前认知很模糊的知识讲的很清楚,所以我以C语言数据类型为主要回顾全书。

C语言程序基础

基础知识

运行过程: 编辑(源代码)-> 编译(目标程序".obj")-> 连接(连接目标程序和库函数,解决函数调用)-> 执行(运行上一步的生成的程序)
  • C语言规定只要参与计算的值中有一个是实型,结果为double型。
stopselect条件运算符"? :"
表达式1 ? 表达式2 : 表达式3
/*功能是先计算表达式1的值,为真(非0)取表达式2的值,否则取表达式3的值

格式化输入输出

scanf()如果格式控制部分只包含格式控制字符,则输入的时候数据之间可以有任意个空格;负责应该严格按格式控制部分格式输入。如:
scanf("%d%d%d", &a, &b, &c);		// 3(回车空格tab)4(回车空格tab)5/回车
scanf("%d,%d,%d", &a, &b, &c);		// 只能是:3,4,5/回车
scanf("%d:%d:%d", &a, &b, &c);		// 只能是:3:4:5/回车
常用格式:
格式字符 含义
%d(或%i) 带符号十进制数
%x(或%X) 无符号十六进制数(不输出前导符0x)
%o 无符号八进制数(不输出前导符0)
%u 无符号十进制数
%f 十进制实数(隐含输出6位小数)
%e(或%E) 指数形式输出实数(隐含输出6位小数)
%g(或%G) 自动以%f或%e输出宽度较短的实数,不输出无意义的0
%c 输出单个字符
%s 输出字符串
%% 输出%

变量的作用域和存储类型

  1. 作用域,变量的有效区域局部变量,在定义的局部有用(一对花括号之间)全局变量,所有函数都可以使用,声明在函数外部全局变量分为文件内全局变量和文件间全局变量,文件内部全局变量被文件中的函数共享;文件间全局变量是指将全局变量声明为extern,以供多个源程序文件访问,这样就将全局变量的作用域范围扩大了。
  2. 存储类型 auto:C语言的默认存储类型 register:寄存器变量,使用时不访存直接从寄存器中读取(循环控制变量可考虑) static:静态局部变量在程序执行之前创建 extern:外部变量,常见于大项目中在头文件中声明常量和函数时用

预处理命令

  1. 宏定义
    #define 标识符	字符串				// 不带参数的宏定义一般形式
    #define 宏名(形参列表) 字符串	  // 带参数宏定义的一般形式 
    
    宏定义行末不加分号,加了就连分号一起置换 宏定义必须在函数意外,作用域为定义开始到源程序结束,如果提前结束使用#undef命令
  2. 文件包含(#include)可以将另一个源文件的全部内容都包含进来,它是一个编译预处理命令,编译预处理是在编译之前进行的。#include <>表示直接到指定的标准包含文件目录中寻找包含文件#include ""表示先在当前目录中找,若找不到再到标准包含文件目录中找,并且”“中可以加路径#include 预处理命令可以包含传递,a包含b,b包含c,则a包含c
  3. 条件编译,源程序在编译时有选择的编译,有三种形式:
    // 第1种,标识符定义了编译否1则编译2(标识符一般用#define命令定义)
    #ifdef 标识符
    	程序段1
    #else
    	程序段2
    #endif
    // 第2种,标识符没有定义了编译1则编译2
    #ifndef 标识符
    	程序段1
    #else
    	程序段2
    #endif
    // 第3种,表达式为真编译1否则编译2
    #if 表达式
    	程序段1
    #else
    	程序段2
    #endif
    

函数

库函数

每个库函数对应一个头文件,头文件包含了所有函数的函数原型,以及这些函数所需的各种数据类型和常量定义,头文件一般命名为".h"。通过预处理命令#include调用。 常用库函数: math.h::数学函数 stdio.h:输入/输出函数 ctype.h:字符函数 string.h:字符串函数 time.h:时间函数 stdlib.h:其它函数,都是标准库函数都是不容易归到前面某一类中 malloc.h/mallo.h:动态内存分配函stopselect数如下:
void *calloc(unsigned int n, unsigned int size);
// 分配n个数据项连续的连续地址空间,每个数据项大小为size;返回起始地址,不成功返回NULL
void free(void *p);
// 释放p所指向的内存区
void *malloc(unsigned int size);
// 分配size个字节的存储区;返回起始地址,不成功返回NULL
void *realloc(void *p, unsigned int size);
// 将p所指的内存区改为size,size可比之前大或小;返回值为指向该内存区的指针

函数原型与声明

C语言为保证一致性,在函数调用之前对函数进行声明,指出函数返回值类型和参数类型和个数。函数声明一般形式:
函数类型 函数名(形参类型1 形参1, 形参类型2 形参2, .....)
// 可简化成下面形式:
函数类型 函数名(形参类型1, 形参类型2, .....)
函数声明的位置:一种是在主调用函数中对被调用函数进行函数声明;另一种是在所有函数外部进行函数声明(推荐使用)。

数组

普通数组

一维数组的定义:
类型标识符 数组名[常量表达式];
// 常量表达式只能是整型常量,可以用#define语句来定义整型常量,不能用变量定义
C语言编译系统不检查越界错误,所以要始终注意数组是不是越界了。 一维数组初始化:
// 对static数组自动会自动赋予统一的初始值
static int a[5];	// 等价于static int a[5]={0, 0, 0, 0, 0};
int a[] = {0, 1, 2, 3, 4}	// 等价于int a[5] = {0, 1, 2, 3, 4};
// 数组只有在初始化的时候可以整体赋值,在使用的时候不能整体赋值
int a[]={1, 2, 3, 4}, b[4];
b = a;	// 该语句是错误的
多维数组和一维数组基本相似,二维数组在C语言中一般是按行优先存储的。

字符数组 VS 字符串

C语言中没有字符串变量,字符串存放在字符数组中。C语言规定用空操作符'\0'作为字符串结束标志,所以字符串最后一个字符为'\0'。程序在定义的时候会自动在字符串后加上'\0',并且'\0'不计入字符串长度;但是'\0'会占用一个字符空间,所以在定义字符数组是大小应该是字符串长度+1。 初始化:
// 字符数组初始化
char c[5] = {'C','h','i','n','a'};
// 字符串初始化
char c[] = {"China"}; // 或写为 char c[] = "China";
// 上面定义等价于 char c[6] = "China";
// 也等价于 char c[6] = {'C','h','i','n','a','\0'};
字符串的输入输出 用scanf()输入时,空格和回车都会作为字符串分隔符,所以输入的内容不能包括回车和空格;输入有空格字符串时可以使用gets()函数,它可以读取空格直到遇到回车为止; 用%s格式输入字符串时,输入项是字符数组名,而不是数组元素也不是数组名前夹取地址符; 用printf()的%s格式输出是输入项是字符数组名,不是数组元素名;例如:
// 注意不同格式化对应不同的输入项
int str[], ch;
scanf("%s", str);
printf("%s", str);
scanf("%c", &ch);
printf("%c", str[0]);
常用字符串库函数(都包含在头文件string.h中)
strcpy(字符数组1, 字符数组2);
// 将2复制到1中,字符串不能直接赋值
strncpy(字符数组1, 字符数组2, n);
// 将2中前n个字符复制到1中
strcat(字符数组1, 字符数组2);
// 将2追加到1后面,字符数组1必须足够大
int strcmp(字符串1, 字符串2);
// 1和2比较,从左到右ASCII码逐个比较
int strlen(字符串);
// 字符串长度
strupr(字符串);
strlwr(字符串);
// 转换大小写

复杂构造数据类型

结构体

同种类型的数据可以用数组来描述,在描述同一个对象中不同数据类型时可以用结构体数据类型。 定义
struct 结构体名
{
    类型标识符1 成员名1;
    类型标识符2 成员名2;
    ………
    类型标识符n 成员名n;
}
结构体类型在使用之前应先定义其类型结构,在定义该类型变量才能使用。 定义结构体类型变量方法
  1. 先定义结构体再定义结构体变量
    struct student
    {
        int num;liexing
        char name[20];
        float score;
    };
    struct student stu1, stu2;
    // 关键字struct要与结构体名一起使用,共同构成结构体类型名
    // 结构体各成员变量在内存中是连续存放的
    
  2. 在定义结构体类型的同时定义变量
    struct student
    {
        int num;
        char name[20];
        float score;
    }stu1, stu2;
    // 在后续中还可以再定义
    struct student stu3;
    
  3. 直接定义结构体类型变量
    struct
    {
        int num;
        char name[20];
        float score;
    }stu1, stu2;
    // 后续不能再定义了
    
结构体类型变量的引用 一般结构体不直接引用变量,而是引用某个成员变量。 成员变量引用方式:
结构体变量名.成员变量名
结构体变量通常不能整体使用,不能整体输入输出,只能对单个成员变量引用。但结构体变量作为函数参数或者赋初始值时,可以整体使用。

共用体

有时候发现两个结构体类型中只有一个或者少数的成员不同,其他大部分成员都是一样的,这是时候如果使用两个结构体会出现大量的代码重复,所以提供一种新的结构类型——共用体类型。 共用体的定义和引用:
union 共用体名
{
	类型标识符1 成员名1;
    类型标识符2 成员名2;
    ………
    类型标识符n 成员名n;
}变量列表;
共用体和结构体定义方式类似,但是结构体的成员分别占据不同的内存单元,是同时存在的;而共用体的所有成员共同占用同一段内存,某一时刻只能有一个成员存在,所以使用共同体时注意存放的是那个成员。另外,结构体所占内存空间为所有成员类型所占内存空间之和,而共同体所占内存空间是占内存最多的那个成员变量类型所占内存大小。

枚举型

实际中有些问题数据的取值是有限的,比如星期,季节。C语言中,用枚举类型定义一些具有赋值范围的变量。枚举类型属于简单基本数据类型,但是用方法和结构体类型相似。 枚举类型定义和引用:
enum 枚举类型标识符{枚举元素1, 枚举元素2,...,枚举元素n};
enum 枚举类型名 变量列表;
// 例如:
enum weekday {sun, mon, tue, wed, thu, fri, sat};
enum weekday day;
枚举类型的引用和普通变量的引用一样,但是要注意枚举类型不能取表中罗列意外的值。 说明:
  1. 枚举类型安常量处理,系统自动复制,比如枚举元素sun值为0,mon为1,以此类推。
  2. 接上一条,值可认为指定,如:
    enum weekday{sun=7, mon=1, tue, wed, thu, fri, sat};
    
  3. 枚举值可以做判断比较
    if (day == sun)
    if (day > mon && day < fri)
    

指针

C语言中变量一旦被定义系统就为其分配确定的地址,通过两种方式存取该值,一是通过变量名;二是通过变量地址。不同变量占用不同的内存单元,并且所占内存单元一定是连续的。在知道变量数据类型(数据占内存单元数)和数据首地址(指针)就可以完整存取变量,所以指针变量也有类型,只是该类型指的是指向数据的类型。 定义形式:
类型 *变量名 [= 初始值]
指针运算符: *用在表达式中(而非定义中)表示的是指针运算符表示取地址中的内容;在变量前加符号&表示该变量的地址。

指针+数组

数组名表示数组在内存中的首地址,因此数组名可以理解为一个指针,不过数组名是一个指针常量,不能改变。可以用数组名赋值给指针变量,找到数组首地址从而找到数组每个元素。
int a[5];
int *p;
p = &a[0];	// 等价于 p = a;
指针变量的加法:
p的新值 = p的旧值 + n*sizeof(指针基类型)
/* 从整体上看,把指针加1相当于把指针向后移动指针所指数据类型的大小的地址单元,
   所以指针很适合用来操作数组,使用指针是目标程序质量高(内存少运行快)	*/
使用指针表示数组注意:
// 可以改变指针变量不能改变指针常量
p++;	//合法
a++;	//不合法
*(p++)等价于*p++
*(p++)和*p++不同
对于一个二维数组
int a[m][n];
a[i] 等价于 *(a+i)
&a[i] 等价于 a+i
a[i][j] 等价于 *(*(a+j)+j)

指针+函数

一个例子说明(交换a,b值):
// 下面这个主调函数中值没有交换,实参到形参的值传递是单向的
// 一个函数中只能return一个变量,不能通过return来解决
void swap(int x, int y) {
    int temp;
    temp = x;
    x = y;
    y = temp;
}
void main() {
    int a = 5; b = 10;
    swap(a, b);
}
// 下面这个主调函数中的值发生了改变,同时注意,*在定义是是指针变量,在操作中相当于取地址运算符
void swap(int *x, int *y) {
    int temp;
    temp = *x;
    *x = *y;
    *y = temp;
}
void main() {
    int a = 5; b = 10;
    swap(&a, &b);
}

指针+字符串

188用字符指针处理字符串直接是指针+1指向下一个字符,指针处理连续的内存单元很方便。 当用%s格式输出时,会一直打印内存知道遇到'\0\'结束。 字符指针于字符数组的区别
  1. 字符数组一旦定义存储空间就是固定的可通过数组名访问;字符指针变量只是地址,改变后不指向原来的内容。
  2. 字符数组只能各个元素单独赋值;字符指针可以整体赋值。
  3. 字符数组定义的时候分配内存单元有确定的地址;字符指针定义没有确定的地址值,使用前应该先指定,如:
    char *a;
    scanf("%s",a);
    // 上面的是错误的,因为指针变量a没有指定字符存储的内存单元,不能完成输入
    // 应该为下面这样
    char *a, str[10];
    a = str;
    scanf("%s", a);
    
将字符串作为函数参数 将字符串从一个函数传到另一个函数,可以用传地址的方法,也就是用字符数组名或者直线该字符串的指针作为参数。在被调用的函数中,可以改变字符串的内容,在调用函数中可以得到改变的字符串。
// 以字符串a和b的内容复制为例
// 使用字符数组名作为参数
void main() {
    char a[] = "linmao";
    char b[] = "maolin";
    copy_string(a, b);
}
void copy_string(char to[], char from[]);
// 使用字符指针作为参数
void main() {
    char *a = "linmao";
    char *b = "maolin";
    copy_string(a, b);
}
void copy_string(char *to, char *from);

指针+结构体

定义结构体的指针p以后,*p相当于结构体变量,引用成员变量是可以用"(*p).成员名"表示,也可以用"p->成员名"来表示。 引用结构体变量的成员变量三种方式:
结构体变量.成员名
(*p).成员名
p->成员名
对于指针始终要注意两点,第一是指针当前指的位置;第二是指针的类型。知道当前指的位置就知道指针在内存中的位置,知道指针的类型,就知道指针在内存中位置和接下来的几个单元的内容表示该指针表示的内容。(所以在此有感而发:说指针是指向地址的地址不准确,应该是指向地址的地址和连续单元的数量)例如:
// student上面定义的那样
struct student stu, *p;
//下面的赋值是错误的,因为p为结构体类型,num为int类型,不能将num地址赋给p
p = &stu.num;
// 下面这样才是正确的
int *ip;
ip = &stu.num;

文件系统

常用文件操作函数

// 打开指定文件
FILE *fopen("文件名", "打开方式")
// 返回:成功返回缓冲区首地址,否则返回NULL

// 关闭fp所指文件
int fclose(FILE *fp)
// 返回:正常关闭返回0,否则返回非0值

// 将字符(第一部分参数)写入fp所指问价
int fputc(int ch, FILE *fp)
// 返回:成功返回字符ASCII码值,失败返回EOF(该符号在stdio.h中声明,值为-1)

// 从fp所指文件中读出一个字符,字符由函数返回
int fgetc(FILE *fp)
// 返回:成功返回所读字符,遇文件结束返回EOF

// 从fp所指文件读取n-1个字符,并将这些字符放到以str为起始地址的内存单元中,如果在此之前遇到换行或EOF,提前结束,读取的字符串末尾自动加'\0'字符
char *fgets(char *str, int n, FILE *fp)
// 返回:成功返回字符串首地址,与文件结束或出错返回NULL

// 向fp所指文件中写入以str为首地址的字符串
int fputs(char *str, FILE *fp)
// 返回:成功返回0,否则返回非0值

// 格式化读写
int fprintf(FILE *fp, 格式字符串, 输出列表);
int fscanf(FILE *fp, 格式字符串, 输入地址列表);

//数据块读写
int fread(void *buffer, int size, int count, FILE *fp);
int fwrite(void *buffer, int size, int count, FILE *fp)
赞(0) 打赏
转载请注明出处:LinMao's Blog(林茂的博客) » 《C语言程序设计》

评论 抢沙发

静态归档版本,评论功能已关闭。
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

LinMao's Blog(林茂的博客)

了解更多联系我们

觉得文章有用就打赏一下作者吧~

支付宝扫一扫打赏

支付宝

微信扫一扫打赏

微信