1. 中央处理器CPU由运算器和控制器组成。
控制器:程序计数器指示下一条执行指令的存储地址,从存储器中取得指令放在指令寄存器,由指令译码器将指令中的操作码翻译成相应的控制信号,再由控制部件将时序控制电路产生的时钟脉冲与控制信号结合起来,控制各个部件完成相应的操作。
2. 十进制整数转换成r进制数:除r取余法(倒着写)
十进制小数转换成r进制数:乘r取整法(顺序写)
3. 符号常量:#define 标识符 常量
4. 类型限定:(1)const限定:在变量定义前加上const修饰,称为只读变量或常变量,在程序运行期间不能被修改。必须在定 义时被初始化。 const int i = 6;
(2)volatile限定:隐式存取变量,在程序运行期间会隐式的(不明显的)被修改;
5. 位运算操作(二进制数操作)会连同符号位一起执行,所以位运算符一般使用无符号整型。
(1)按位与(&):有一个0则为0,双1得1。 正数运算直接对应二进制按位与,负数则用补码(原码取反+1)形式运算。
按位与进行的特殊运算:指定二进制位清零、取整数中指定的二进制位、保留指定位。
方法:构造一个与之按位与的整数,清零位为0,其余位为1(或所取位为1,其余位为0;或保留位为1, 其余位为0)。
(2)按位或(|):有1个1则为1,双0为0。
按位或用来设置一个整数中指定二进制位为1,方法同上:设置位为1,其余位为0。
(3)按位异或(^):相同为0,相异为1。 特别的:a&b + a^b 等于 a|b。
异或的作用是:判断两个对应的位是否为异。
特殊运算:a:使指定位翻转:方法同上,翻转位为1,其余位为0。
b:两个值互换: a = a^b, b = b^a, a = a^b
实现两个整型变量a 和 b 的值相互交换:
法1:int t; t = a, a = b, b = t;
法2:a = a+b, b = a-b, a = a-b;
6. 左移相当于乘2,右移相当于除2. 原理:操作数的所有二进制位向左移2位,左边的2位被移除,右边补2位0。右移同理。
7. 位运算的类型转换:右端对齐,正数左边补满0,负数左边补满1,无符号整数左边补满0。
8. 声明和定义不能放在语句中间。C语言规定声明部分必须出现在所有可执行语句的前面。
9. 字符串常量的拆分办法是使用反斜杠(\)行连接符,其作用是用程序的下一行(从第一列开始)替换当前的行连接符。
10. 字符输出:putchar(c) // 输出的C值对应的ASCII符号。函数调用时c可以是常量,变量或表达式,可以是整型数据,字符型 数据或转义字符。
11. 字符输入:getchar() //通常将getchar的返回值字符型变量或整型变量或作为表达式的一部分,getchar的输入操作步骤是先检查缓冲区是否有字符,若有字符则直接从缓冲区中提取一个字符返回,且缓冲区移向下一个字符;若没有字符则getchar等待键盘输入,直到输入回车键结束等待。
12. 格式化输出: printf()
(1)一些说明:
- 表示左对齐,右边用空格填充;默认为右对齐,左边用空格或者0填充
+ 当输出值是有符号数,则总是加上正负号;默认只在负数前加-
空格 表示 输出值是有符号 数或者正数,则以空格作为前缀加到输出值前
n 或者 0n 或者* 表示输出宽度,第一个用空格填充,第二个用0在左边填充,最后一个设置宽度
.n 精度
(2) 示例:
"%d" 十进制 ld 长整型,hd 短整型,[%4d] 宽度 [%-4d] 左对齐 [%04d] 左边填充0 [%#x] 填充十六进制前缀
[“%* d\n” , 5 ,a ] 由输出项指定宽度 [%8.2d] [%-8.2d] 对整型无作用,右对齐或左对齐输出,空格填充共8位
"%u" 无符号十进制 "%o" 无符号八进制 "%x或X" 无符号十六进制 "%f " dddd.dddd格式
[%*.*f] 由输出项指定宽度与精度 [.2lf] 默认宽度,保留两位小数
"%e或E" 指数 "%g或G" 以f或e输出带符号数值,最简形式
"%c" 单个字符 "%s" 直到第一个非空字符('\0')或满足精度的字符串
[%.3s] 保留字符串的前三个字符 [%6.3s] 宽度为6,精度为3 ,左边补空格
"%%" 输出一个%
13. 格式化输入:scanf(格式控制,输入项列表)
“%d%d%d” 连续输入空格、tab、回车间隔
“%d,%d,%d” 输入时要输入逗号
“%d %d %d” 输入时要输入空格
“a=%db=%dc=%d” 输入时要保持格式一致
“%4d%4d” 指定宽度
scanf(“%1d%*2d%3d”,&a,&b) 禁止符号 比如:输入123456789 输出: a =1 ,b = 456 23读取但是不保存
scanf(“%d%d”,&a,&b,&c) 格式数目小于输入项数,多余输入项未被输入
scanf(“%d%d%d”,&a,&b) 格式数目大于输入项数,崩溃性错误
14. C语言不允许在函数体内嵌套定义函数,而Python则允许。
15. 在C语言中,值传递是唯一的参数传递方式。
16. 可变参数函数:
格式: 返回类型 函数名(类型1 参数名1,类型2, 参数名2,...)
{ 函数体 }
示例: double avg(int first,...)
{
va_list arg_ptr; //定义可变参数列表指针
va_start(arg_ptr,first) // 初始化
i = va_arg(arg_ptr,int) //取下一个参数
va_end(arg_ptr) //清空参数列表
}
17. 函数原型: 返回类型 函数名( 类型1 参数名1, 类型2,参数名2); //通常写在函数头下面
18. 易错的知识点:int i =1,j=2; printf(''%d,%d,%d\n'', i=i+j, j=j+i, i=i+j); 输出结果为 8,5,3 //从右向左计算实参
19. 内联函数: inline 返回类型 函数名(形参列表) //使用内联函数就没有函数的调用了,在编译时会将被调函数的代码直接嵌入到主调函数中,取消调用这个环节。有以空间换时间的意思。不允许使用在循环语句和switch语句中,递归函数中也不能被用来当作内联函数。
20. 使用extern 声明将变量或者函数实体的可见区域往前延伸,称为前置声明。
21. 程序映像:C语言经过编译连接后成为二进制形式的可执行文件称为程序映像。
22. 程序在内存中的布局由五个段组成。
(1)代码段(text segment):存放程序执行的机器指令,C语言的表达式,语句,函数等编译成机器指令就存在text段。
(2) 已初始化数据段(data setment): 存放所有已赋初值的全局和静态变量、对象,也包括字符串、数组等常量。持久性。
(3) 未初始化数据段(bss segment):存放C语言中未赋初值的全局或静态变量。
(4)栈(stack):存放C程序中所有局部的非静态变量,临时变量,包括函数的形参和函数返回值。临时性。
(5)堆(heap):存放C程序中动态分配的存储空间。临时性。
其中:栈和堆的共同特点是动态储存。
不同之处在于分配方式不同:栈是编译器根据程序代码自动确定大小,堆是完全由程序员指定分配大小,何时分 配,何时释放。
23. 自动对象(auto 类型 变量名):其存储方式是自动存储,进入函数时分配空间,结束函数时释放空间。默认的存储类型修饰就是auto。
24. 寄存器变量(类型,变量名): 动态存储。当编译器不能使用寄存器(数量有限)时,自动转换成auto
25. 函数声明和函数定义的区别:函数的声明是使用函数原型,声明用于向程序表明对象的类型和名字。
函数的定义包括函数体,定义用于创建对象并为对象分配实际的存储空间。
26.对象声明和对象定义的区别:对象定义时就 创建了与对象类型对应的存储空间,还可以初始化对象。定义包含声明,所以可 在其后的作用域内使用,如果想在定义的前边使用对象,就应该在前面使用extern给出对象声 明。
注:当extern 声明出现在全局作用域上,extern声明允许出现初始化。(只能出现一次)
一个文件含有对象的定义,使用该对象的其他文件则包含该对象的声明。
27. 内部函数:在函数定义前加上static 修饰
28. 外部函数:在函数定义前加上extern 声明,C语言所有的函数本质上都是外部函数,所以extern可以省略。
29. 多文件结构:
(1)头文件: #include<stdio.h> //使用系统函数时
#include"stdio.h" //使用自定义头文件或附加库头文件
(2)编译器的功能:预编译头文件,增量编译(检测未修改时使用上次的),编译缓存(将前面的编译结果缓存下来,根据文件 内容判断是否重新编译)
(3)自定义一个main.h,文件内容比如是#define PI 3.14 ,然后就可以在别的xxx.c 文件中使用#include"stdio.h"来引入。
30. 预处理命令:
(1)一些命令: #define 宏名(参数表) 字符文本
#undef 宏名 //取消已有的宏定义
使用行连接符(\)得到多行宏定义,注意最后要连接一个空行
"#" 出现在宏定义字符文本中时,作用是对参数替换后再加双引号"" 括起来,变成 "参数" 。
比如#define PRINT_MSG(x) printf(#x); PRINT_MSG(Hello World) ;宏替换为printf("Hello World")
"##" 将两个字符文本连接成一个字符文本。
比如#define SET(arg) A##arg = arg; SET(1) 宏替换为:A1=1;
#error 错误信息
#pragma token-string // token-string为编译器要处理的命令 ,如:pragma once
#line 常量值 "filename" //强制编译器按照指定的行号对源程序代码重新编号,说明行号来自filename。
比如:#line 151 "copy.c"
(2)宏定义与函数调用的区别:
宏定义只是参数文本替换,而函数调用则是实参值传递到形参;
函数调用在程序运行期间运行,会为形参临时分配内存单元,宏在预处理阶段替换,无内存分配,返回值等概念;
函数调用对形参和实参要定义类型,宏定义不存在类型问题;
宏替换会占用编译时间,函数调用则会占用运行时间;
宏与内联函数区别在于宏在预处理器替换不加任何检验,而内联函数通过编译器在用的过程中像宏一样展开,取消了函 数的参数入栈,减少了调用的开销。内联函数要做参数类型检查。
(3)预定义宏
__FILE__ 编译程序文件名 __LINE__当前源代码行号 __DATE__ 编译程序日期 __TIME__ 编译程序时间
(4)头文件路径可写成绝对路径或编译器系统INCLUDE路径下。
(5)文件包含的重复包含问题解决办法:
使用条件编译:#if !defined(XXX) #define XXX .......#endif //将 头文件内容放在一个条件编译块中。
使用#pragma once ..... //表示在编译一个原文件时,只对该文件包含(打开)一次。
(6) 条件编译:希望代码可以在一定条件下才可以编译
形式1:#ifdef 条件字段 程序代码段1 #endif
形式2:#ifdef 条件字段 程序代码段1 #else 程序代码段2 #endif
ifndef:检测是否条件字段没有被定义过,与#ifdef相反, 示例:#ifdef XXX #ifndef SSS ..... #endif #endif
形式3:#if 常量表达式1 程序代码段1 #elif 常量表达式2 程序代码段2 #else 程序代码段3 #endif //此处的常量表 达式为#define定义的常量
31. 数组
(1)int A[3][4][5] 和A[60]等价,元素 A[i][j][k] = A[i*4*5+j*5+k]
(2)数组作为函数的参数时,实参和形参不仅要同为数组,而且类型也应该保持一致。
(3)数组参数的传递中本质上是将被调用的实参数组的首地址(即一个数值)像变量实参那样值传递到形参中。
a. 其实本质就是形参数组就是实参数组,所以在被调用函数中使用形参就是在间接使用实参。
b. 形参数组的长度与实参数组长度可以不相同,也可以不用给出,因为函数调用时不会为形参数组分配存储空间。
c. 多维数组作为参数时,除了第一维可以不同,其他维必须相同,也必须给出其他维。
d. 防止数组越界的一个方法是在函数调用时再给出一个参数来表示实参数组的长度。
(4)字符数组在使用时只能逐个引用字符元素的值而不能一次性引用整个字符数组对象。
32. 字符串 (以'\0'字符作为结束符,空字符也占位,字符串存放在字符数组中)
(1)在定义时要保证数组的长度大于字符串的长度,防止数组越界;
(2)字符串初始化:char s[] = "XXXXXXXXXXX"
(3)字符串输入输出:
a. 通过遍历数组元素,使用%c格式逐个字符输入输出
b. scanf("%s",str) //不使用&,不能输入空格,Tab,回车,在输入完成后在字符串末尾添加空字符
printf("%s",str) //遇到第一个空字符就会结束,而不管是否到了字符串末尾
c. 使用字符串输入输出函数:
char *gets(char *s) //比如 gets(str) ,可以输入空格和Tab,但是不能输入回车,输入完成后,会自动在末尾添加空字符
int puts(char *s) // 比如:puts(str), 如果字符串没有空字符,会引起数组越界,puts输出的字符不包含空字符。
(4)字符串数组(二维字符数组):
char A[3][20] = {{"XXXX"},{"XXXX"},{"XXXX"}} //字符串数组二维初始化形式,可省略一维长度
char A[3][20] = {"XXXX","XXXX","XXXX"} //字符串数组一维初始化形式
示例:char A[3][80]; scanf("%c",A[0]); //输入第0个字符串 gets(A[1]); // 输入第1个字符串 printf("%s",A[0]); puts(A[1]);
(5)字符串处理函数
a. 字符串复制函数 //头文件为string.h
strcpy(str1,str2) //复制str2到str1 或者 strcpy(str1,"java")
strncpy(str1,str2,4) //将Str2中不超过n个字符的字符串复制到str1中,当str2没有n个的时候,会复制整个str2剩余位用空 字符填充直到n个。
示例:strncpy(s1,s2,sizeof(s1)-1);// n最大为s1存储空间长度减1
s1[sizeof(s1)-1] = '\0' //最后放上空字符串结束
b. 字符串连接函数
strcat(str1,str2); //在str1后边连接str2,str2未变化
strncat(str1,str2, n) //不超过n个字符连接到str1后
c. 字符串比较函数
strcmp(str1,str2) == 0 //比较字符串相等
strcmp(str1,str2) != 0 //比较字符串不相等
strcmp(str1,str2) > 0 //比较str1大于str2
strcmp(str1,str2) < 0 //比较str1小于str2
d. 字符串长度函数
n= strlen(str); //返回字符串长度。比如char str[10] = "java", n=strlen(str) n=4
n=sizeof str; //返回字符数组长度 n= 10
e. 字符串转成数值函数(头文件为stdlib.h,遇到不合法就结束转换)
f = atof("123.456") //f = 123.456
i = atoi("123") // i = 123
i = atoi("-123") // i = -123
f. 数据写入字符串的格式化输出函数sprintf
sprintf(str,"%d*%d",2,3,2*3) //输出结果不显示,会存储在str中(str[4] = 6),然后会在数据后面增加空字符,使s变成字符串
g. 从字符串读入数据的格式化输入函数sscanf
sscanf("12 34","%d%d",&a,&b); 读入a=12,b=34
33. 排序:
(1)交换类排序:(借助数据元素之间的相互交换实现排序)
a. 冒泡排序: 通过相邻两个记录之间的比较和交换,小的往左移(上升),大的往右移(下沉)
#include<stdio.h>
#define N 10
int main()
{
int A[N],i,j,t;
for(i=0,i<N;i++)
{
scanf("%d",&A[i]); //输入N个数
}
for(j=0;j<N-1;j++) //冒泡排序法
for(i=0;i<N-1-j;i++) //一趟冒泡排序
if(A[i]>A[i+1]) //A[i]与A[i+1]比较 < 表示升序, >表示降序
t = A[i];
A[i] = A[i+1];
A[i+1] = t; //交换
for(i=0;i<N;i++)
{
printf("%d",A[i]);
}
return 0;
}
b. 快速排序:
¥1:我们从待排序的记录序列中选取一个记录(通常第一个)作为基准元素(称为key)key=arr[left],然后设置两个变量,left 指向数列的最左部,right指向数据的最右部。
¥2: key首先与arr[right]进行比较,如果arr[right]<key,则arr[left]=arr[right]将这个比key小的数放到左边去,如果 arr[right]>key则我们只需要将right--,right--之后,再拿arr[right]与key进行比较,直到arr[right]<key交换元素为止。
¥3: 如果右边存在arr[right]<key的情况,将arr[left]=arr[right],接下来,将转向left端,拿arr[left ]与key进行比较,如果 arr[left]>key,则将arr[right]=arr[left],如果arr[left]<key,则只需要将left++,然后再进行arr[left]与key的比较。
¥4 :然后再移动right重复上述步骤。
/*
* 快速排序
*
* 参数说明:
* a -- 待排序的数组
* l -- 数组的左边界(例如,从起始位置开始排序,则l=0)
* r -- 数组的右边界(例如,排序截至到数组末尾,则r=a.length-1)
*/
void quick_sort(int a[], int l, int r)
{
if (l < r)
{
int i,j,x;
i = l;
j = r;
x = a[i];
while (i < j)
{
while(i < j && a[j] > x)
j--; // 从右向左找第一个小于x的数
if(i < j)
a[i++] = a[j];
while(i < j && a[i] < x)
i++; // 从左向右找第一个大于x的数
if(i < j)
a[j--] = a[i];
}
a[i] = x;
quick_sort(a, l, i-1); /* 递归调用 */
quick_sort(a, i+1, r); /* 递归调用 */
}
}
(2)选择类排序:(选择排序法和堆排序,从无序序列元素中依次选择最小或者最大的元素组成有序序列)
a.选择排序法:
从n个记录中选出最小值x,将它放到第一位,然后再剩余的记录中再次选择最小值,放到第二位,依次类推。
int i, j, k, t;
for(i=0; i<n-1; i++)
{
k=i; //选择第一个元素索引,比较所有记录的大小,最小的放到k位
for(j=i+1;j<n;j++)
if(A[j]<A[k]) // < 表示升序, >表示降序
k=j;
if(i!=k)
t = A[i];
A[i] = A[k];
A[i] = t;
}
(3)插入类排序:(插入排序法和希尔排序法,将无序序列中的元素依次插入到有序序列中)
a. 插入排序法:
通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
¥1、将待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列;
¥2、取出下一个元素,在已经排序的元素序列中从后向前扫描;
¥3、如果该元素(已排序)大于新元素,将该元素移到下一位置;
¥4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置;
¥5、将新元素插入到该位置后;
¥6、重复步骤2~5。
/* 插入排序*/
int i,k,t;
for (i = 1; i < n; i++)
{
t=A[i]; //存放待排序序列
k = i-1; //有序序列的最后一个元素位置
while ( k >= 0 && t<A[k])
{
A[k + 1] = A[k];
k--;
}
A[k + 1] = t; //将待排序元素插入数组中
}
34. 生成随机数的例子
#include<time.h>
#define N 10
int main()
{
int A[N],i;
srand((unsigned int)time(0)); //设置随机数种子
for(i=0;i<N;i++){
A[i] = rand()%100;
printf("%d",A[i]);
}
}
35. 指针
(1)指针:一个对象的地址称为该对象的指针。
(2)指针变量:用来存放对象地址的变量。
(3)纯指针:void *p,可以保存任何对象的地址,不允许使用void * 指针操纵他所指向的对象。
(4)获取对象地址:int a=20,*p; p = &a; //称指针变量p指向a ;*p就是a的值;&*p就是&a(得到a的地址);*&a先得到a的地址 , 再得到a;等价于a。
(5)指针变量的初始化与赋值:
int a; int *p = &a; //p的初值为变量a的地址
p=0; // 指针允许0值常量表达式
int *p = NULL; //等价于int *p = 0;
(6)指针的有效性:
① 空指针:指针的值为0,称为0值指针,空指针是无效的
②野指针:指针未进行初始化,或未赋值,或指向未知对象
③迷途指针:一个指针曾经指向一个已知对象,在对象的内存释放以后,虽然该指针仍是原来的内存地址,但是所指是一 个未知对象。
(7)指针运算:
① 假如:int x, n=3,*p = &x
p+1 或 p+n: 表示指针指向存储空间中变量x后的第一个或者第n个int型存储单元,
p-1 或 p-n: 表示指针指向存储空间中变量x前的第一个或者第n个int型存储单元。
p+0 、p-0 、 p: 均指向同一个对象。
p++:运算后表达式的值(临时指针对象)指向变量x,p指向变量x后的第一个int型内存单元
++p:运算后表达式的值(临时指针对象)和p指向变量x后的第一个int型内存单元
p--:运算后表达式的值(临时指针对象)指向变量x,p指向变量x前的第一个int型内存单元
--p:运算后表达式的值(临时指针对象)和p指向变量x前的第一个int型内存单元
② int a =100,*p = &a;
(*p)++: 等价于a++,p的值不变
*p++: 运算后表达式的值为a,p指向下一个int型内存单元
*++p: p先指向下一个int型内存单元,然后再取值。
③ 两个指针相减:得到的结果为两个指针之间对象的个数
④ 关系运算:比较的都是地址值的大小
(8)指针的类型转换: (转换类型 *) p // 产生一个临时指针对象指向转换类型,地址值与p的地址值相同,但p的指向类型和地 址值都不变。
(9)指针的赋值运算:前提是赋值运算符两边的操作数必须是相同指向类型
(10)void * 指针不能做算术运算,但是可以做关系运算。表示两个指针的地址值比较。
(11)指针的const限定:
① 指向const对象的指针:const 指向类型 *指针变量名
不允许通过指针来改变所指向的const对象的值,但是p可以重新赋值,使他指向另一个const对象 ;
② const指针:指向类型 *const 指针变量名
不允许使p再被赋值指向其他对象
③指向const对象的const指针:const 指向类型 *const 指针变量名
既不能修改p所指向对象的值,也不允许修改该指针的指向。
注意:对于上述①的概念:
不能把const对象的地址赋给非const对象的指针变量;
不能使用void *指针保存const对象的地址,而必须使用const void * 指针保存const对象的地址;
允许把非const对象的地址赋给指向const对象的指针;
不能保证指向const的指针所指对象的值一定不被其他方式修改。
(12)指向一维数组元素的指针:
① int *p = a; //使p指向数组a
② p = a 等价于 p = &a[0] //p指向了数组首元素的地址
③ &a[i] 等价于 p+i 等价于 a+i //表示a[i] 的地址
④ *(p+i) 等价于 *(a+i) // 表示a[i]的元素值
⑤ 访问一个数组元素a[i]的方法:
数组下标法:a[i] //a[i]是一个指针常量,其值在运行期间是固定不变的
指针下标法:p[i] // p是一个指针变量,可用p++使p值不断改变从而指向不同的元素
地址引用法:*(a+i)
指针引用法:*(p+i)
⑥ *p++ : 自右向左。 等价于 *(p++) 先得到p所指向的元素的值(即*p),再使p指向下一个。
*(++p):先使p加1,再取*p
(*p)++: p所指向的元素加1, 等价于a[0]++,运算后p值不变
⑦ 若p = &a[i]:
*(p++) 等价 a[i++]
*(++p) 等价 a[++i]
*(p--) 等价 a[i--]
*(--p) 等价 a[--i]
(13)指向多维数组元素的指针
① a 表示二维数组首元素的地址,这个首元素是由4个整型元素所组成的一维数组。
② a+i 表示第i行(a[i])的首地址 等价于 &a[i] , 他们均指向第i行(一个一维数组) ,等价于 &a[i][0]
③ a[i] 表示的是第i行首元素的地址,&a[i] 表示的是第i行的地址。两者值相同,但是含义不同。 a[i]指向第i行的首元素(即指向第0列),&a[i]指向行。
④ &a[i]+1 指向下一行地址
⑤ a[i]+1 第i行下一列(第1列)元素的地址
⑥ 二维数组的地址形式:
地址形式 | 含义 | 等价地址 |
---|---|---|
a | 既代表二维数组对象,又是第0行的首地址(指向一维数组a[0]) | &a[0][0] |
a[0]、*(a+0)、*a | 既代表第0行(一维数组),又是第0行第0列元素的地址 | &a[0][0] |
a[i]、*(a+i) | 既代表第i行(一维数组),又是第i行第0列元素的地址 | &a[i][0] |
&a[i]、a+i | 第i行的首地址 | &a[i][0] |
a[i]+j、*(a+i)+j | 第i行第j列元素的地址 | &a[i][j] |
⑦ 访问一个二维数组a[i][j]:
数组下标法:a[i][j]
指针下标法:p[i*M+j] //当作一维数组
地址引用法:*(*(a+i)+j) 或*(a[i]+j)
指针引用法:*(p+i*M+j)
⑧ p - a[0] 表示 p当前指向的元素与a[0][0]之间的元素个数
(14)数组指针: int (*p)[4] 或 int (*p)[3][4]
① 数组指针本质上是一个指针,编译器分配相应的类型所占的字节,而不是按照数组长度来分配空间
② 若p指向一个数组,则*p就是该数组。则*p就是数组a[0],不是a[0]的元素。
通过p访问数组元素a[0][j]: (*p)[j] 等价于 a[0][j] *(*p+j) 等价于 *(a[0]+j)
(15)指针数组:int *s[4] = {a[0],a[1],a[2],a[3]} //一维指针数组的初始化
① 通过指针数组s的元素访问二维数组a的形式:
s[0]:指向a[0][0]
*s[0]:等价于a[0][0]
s[i]+j:指向a[i][j]
*(s[i]+j)、*(*(s+i)+j)、s[i][j]:等价于a[i][j]
② s 是数组名,不能作为左值或者进行自加自减运算。
(16)指向指针的指针(二级指针)
① 假设 int a, *p = &a , **pp = &p : //p的初值为a的地址,pp的初值为p的地址
*p:间接引用运算结果为 a
*pp: 间接引用运算结果为 p
**pp:等价于 *(*pp),两次间接引用运算结果为a
pp变量的值就是指针p的地址
p变量的值就是a的地址
② 通过二级指针访问一维指针数组s:
pp = s 、 pp = &s[0]:二级指针pp指向一维指针数组s的首元素地址
pp = s+i、pp = &s[i] 、 pp+i :二级指针pp指向一维指针数组s[i]的地址
*(pp+i)、pp[i] :等价于 s[i]
③ 通过二级指针访问一维指针数组s再间接访问二维数组a:
*(pp+i)、pp[i]:指向a[i][0]
**(pp+i)、*pp[i]、*(pp[i]+0)、pp[i][0]:等价于a[i][0]
*(pp+i)+j、pp[i]+j:指向a[i][j]
*(*(pp+i)+j)、*(pp[i]+j)、pp[i][j]:等价于a[i][j]
(17)指针与字符串
①指向字符串的指针:char *p = "XXXXXX" ; // p存储字符串的首地址
②指向字符数组的指针:char str[] = "XXXX " ,*p = str; //p指向字符串的指针
③printf("%s",p); //%s格式,输出项必须为字符串地址
printf("%c",*p++) //输出结果与上面相同,
注意:如果此时p指向字符串指针,首地址就是字符数组名,如果此时p指向字符串常量,那p在运 行完p++后会变成迷途指针,解决的方法是另外引入一个指针变量来存放字符串常量的首地址。
④ while(*p) //*p 是 *p != '\0'的简写形式。
⑤ 指向字符串数组的指针:字符串数组是一个二维的字符数组。
char *pa[6] = {"XXX","SSS","QQQ","WWW","EEE","RRR"} //pa 为一维数组,有6个元素,每个元素均是一个字符指针
char **pb = pa; //使用字符指针数组,各个字符串按照实际长度存储,指针数组元素只是各个字符串的首地址,不浪费内存
(18)指针与函数
① 指针变量作为形参:void swap(int *p1,int *p2) { 函数体 } //调用时必须使用相同指向类型的指针(或地址)作为函数实参
② 数组作为函数形参:double average( double A[100],int n){ 函数体 } // 等价于double average(double *A, int n)
调用: double X[100],f; f = average(X,100) //X 表示该数组的首地址, A 指向数组X
③四种形式:假设 int a[100], p=a;
实参、形参都用数组名: void fun(int x[100],int n); fun(a,100)
实参、形参都用指针变量: void fun(int *x ,int n); fun(p,100)
实参用数组名、形参用指针变量: void fun(int *x,int n); fun(a,100)
实参用指针变量、形参用数组名: void fun(int x[100],int n); fun(p,100)
④函数指针变量参数的const限定:void fun(const char *p,char m) //fun函数不能通过指针p修改指向的字符串
void fun(char *const p,char m) //不允许在函数内部修改形参指针变量的值
⑤ 字符指针变量作为函数形参:
char *strcpy(char *s1, const char *s2); //复制完成后要在末尾加上'\0',
法二:while(*p1 = *p2) p1++,p2++; 将*p2的字符先赋给*p1,此时*p2作为判断依据,这杨不用给*p2添加结束符
char *strcat(char *s1, const char *s2);
int strcmp(const char *s1,const char *s2)
int strlen(const char *s);
注意:数值0,空字符'\0'、空指针NULL可直接当作逻辑值”假“
⑥ 指向指针的指针变量作为函数形参: void fun(int **x, int **y){ int *t; t=*x, *x=*y,*y = t} //将两个指针的值互换。
⑦ 函数返回指针值:char *substring(const char *str,const cahr *sub){ 函数体 return &a;} //要考虑指针有效性问题
⑧ 函数指针:int (*p)(int a, int b)
示例:int max(int a,int b); //max函数原型
int (*p)(int a,int b); //定义函数指针变量
则: p=max;
通过函数指针调用函数:c = p(a,b); //等价于c = max(a,b);
(19)动态内存:
①分配一个int型内存空间: int *p; p = (int *) malloc(sizeof(int));
②分配50个int型内存空间: int *p; p = (int *) calloc(50,sizeof(int)); //等价于 p = (int *) malloc (50*sizeof(int))
③ 动态内存调整函数:p = (int *) realloc (p,10*sizeof(int)); //将上边分配的调整为10个int整型的内存空间
④动态内存释放函数:free(p); //释放p所指向的内存空间,指针p变成迷途指针
p = NULL; //设置p为0值指针NULL,指针p不是迷途指针
⑤ 动态内存分配和释放必须对应,有分配就必须有释放
⑥ 动态分配数组时,空间大小按元素个数计算,然后按照一维数组方式使用这个数组,最后释放数组空间。
⑦ 带参数的main函数:int main(int argc, char *argv[]) //argc:命令行中字符串的个数,argv:字符串指针数组,用于指向命 令行中各个字符串。
36. 自定义数据类型
(1)结构体对象:
① 一般形式:struct 结构体类型名 { 成员列表 } 结构体对象名列表;
② 结构体对象的内存形式:结构体各成员是根据在结构体声明时出现的顺序依次分配空间的,结构体对象的内存长度是各 个成员内存长度之和,推荐使用sizeof()运算。
③ 字节对齐:编译器会对结构体成员和结构体本身存储位置进行处理,使其存放的起始地址是一定字节数的倍数,而不是 顺序存放,称为字节对齐。
规则:设对齐字节数为n (n = 1,2,4,8,16),每个成员内存长度为Li,Max(Li)为最大成员内存长度。
结构体对象的起始地址能够被Max(Li)所整除;
结构体中每个成员相对于起始地址的偏移量,即对齐值应是min(n,Li)的倍数,若不满足对齐值的要求,编译器会 在成员之间填充若干个字节(称为 internal padding);
结构体的总长度值应是min(n,Max(Li))的倍数,若不满足总长度值的要求,编译器会为最后一个成员分配空间 后,会在其后填充若干字节(trailing padding)
使用预处理命令 #pragma pack(n)可以设定对齐字节数n.
示例:
④ 结构体对象的初始化:struct 结构体类型名 { 成员列表 } 结构体对象名 1 = {初值序列1},.......;
示例: struct tagSTAFF s1 = {1001,"LI Ming",'M',{1998,6,8},2700.0}; //嵌套结构体成员
struct tagSTAFF s1 = {1001,"LI Ming",'M',1998,6,8,2700.0};
⑤ 结构体对象的使用:结构体对象名.成员名
⑥ 结构体对象的输入与输出:scanf("%d%d",&x.birthday.year,&x.birthday.month);
gets(x.name); //字符串成员输入
(2)结构体数组:
①示例:strct tagRECT{ int left,.....};
struct tagRECT rects[ ] = { {1,1,2,3},{3,4,5,6},{.......},{.......}}
引用结构体数组成员: 数组对象[下标表达式].成员名
②结构体数组成员:
struct tagTriangle{ struct tagPOINT p[3]; };
struct tagTRIANGLE tri;
tri.p[0].x = 10, tri.p[0].y = 10; //结构体对象.数组成员[下标表达式] .成员名
③ 指向结构体的指针:
示例:struct tagSTAFF m, *p ; //结构体对象,指向结构体对象的指针
int *p1; //指向no成员的指针类型为int *
char *s1,*s2; //指向name、sex成员
struct tagDATE *p2; //指向birthday
p = &m; //取结构体对象的地址
p1 = &m.no //取no成员的地址
s1 = m.name; //name成员为数组,数组名即是地址
s2 = &m.sex; //取sex成员地址
p2 = &m.birthday; //取birthday成员地址
p为指向结构体对象的指针,通过p引用结构体对象成员的两种方式:
(*p).成员名;
p -> 成员名; //结构体指针 ->成员名 p->no = 1002; p->birthday->year = 2008
④指向结构体数组的指针:
struct 结构体类型名 结构体数组名[常量表达式],*结构体指针;
结构体指针 = 结构体数组名;
示例:
int i = 3, j = 4;
struct tagSTAFF branch[20], *p = branch; //指向结构体数组的指针
p[i] = p[j]; //等价于 branch[i] = branch[j]
*p = *(p+1); //等价于 branch[0] = branch[1]
p[i].no = 1003; //给结构体对象成员赋值
printf("%d\n",(*(p+i)).no); //输出结构体对象成员
scanf("%s",(p+i)->name); //输入结构体对象成员
⑤ 结构体指针成员(存储的是地址)
struct tagDATA1 { int data; char *name;}a={10,"LiMing"},b;
⑥ 结构体与函数
结构体对象作为函数参数:采用值传递方式
结构体数组作为函数参数:采用地址传递的方式,函数调用实参是数组名,形参必须是同类型的结构体指针,传递 的参数时数组的首地址,形参数组的首地址与实参数组完全相同。
结构体指针作为函数参数:采用地址传递方式,实参是结构体对象的地址,形参是同类型的结构体指针。
函数返回结构体对象或者指针:函数返回时复制一次到临时对象中,赋值时再将临时对象又复制一次到左值中。
(3)共用体(成员共享存储空间)
① union 共用体类型名{ 成员列表 };
② 共用体内存长度是所有成员内存长度的最大值,结构体内存长度是所有成员内存长度之和。
③ 定义共用体对象时可以初始化,但只能按一个成员给予初值。 如:union A x = {1234}; 此时A中每个成员都是这个值
④ 共用体对象成员的特点:
修改一个成员会使其他成员发生改变,所有成员存储的是最后一次修改的结果;
所有成员的值都是相同的,区别是不同类型决定使用这个值的全部或者部分;
所有成员的起始地址值都是相同的,因此只按一个成员输入和初始化。
(4)枚举类型
① 示例: enum tagDAYS {MON=2, TUE=3,WED,THU,FRI,SAT,SUN}; //enum 枚举类型名 {命名枚举常量列表};
默认的枚举常量总是从0开始,后续的枚举常量总是前一个的枚举常量加1,如上WED为4,THU为5...
② 枚举类型对象: enum tagDIRECTION { LEFT, RIGHT,.......} dir = LEFT;
(5)位域(指定结构体或共用体占用存储空间的二进制位数)
① struct/union 结构体/共用体类型名 { 位域类型 成员名:常量表达式; //声明位域}
② 位域的长度不能超过其类型对应的存储单元的大小,且必须存储在同一个存储单元,不能跨两个单元. int x: 33 //错误
③ 若匿名位域的位长度为0,则表示其后的位域成员将另起一个存储单元. int :0;//强制后边的位域成员从一个新的int存储
(6)用户自定义类型:
① typedef 已有累类型名 新类型名;
② typedef是存储类别关键字,不能与auto、static、register、extern同时使用。
③ typedef 允许递归声明,也可以一次性声明多个新类型名。
④ typedef声明新类型名的方法:
按变量定义的方法写出定义形式: char *s1,*s2;
将变量名换成新类型名: char *LPSTR, *PSTR;
在前面加上 typedef: typedef char *LPSTR, *PSTR;
37. 文件
(1)文件打开: FILE *fopen(const char *filename, const char *mode);
如:FILE *fp1; fp1 = fopen("IN.TXT","r"); // 打开已存在的文件
(2)文件关闭:int fclose(FILE *stream);
(3)文件状态:① 文件末尾检测:int feof(FILE *stream)
② 出错检测:int ferror(FILE *stream)
③ 错误类型:extern int errno; //声明全局错误变量,根据errno值判断错误原因
④ 文件缓冲: int fflush(FILE *stream) //清除指定文件stream的缓冲区,
如:fflush(stdin); //清空标准输入文件(即键盘)缓冲区
(4)文件读写
① 基本形式:while(!feof(fp)){ } //文件是否到末尾
② 读写字符数据:int fgetc(FILE *stream) //读文件字符数据
int fputc(int c, FILE *stream) //c是输出字符,如:fputc(fgetc(in),out) //从源文件读写一个字符到目标文件
③ 读写字符串数据: char *fgets(char *string,int n, FILE *stream); //读文件字符串
int fputs(const char *String, FILE *stream); //写文件字符串 String 是输出字符串
④ 读写格式数据:fscanf(fp, "%s%d",A[cnt].c,&A[cnt].p); //按地址形式
fprintf(fp,"%s%d",A[i].c,A[i].p);
⑤ 读写数据块:fread(&m,sizeof(int),1,fp) //读1个整型,&m是整型变量的地址
fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
(5)文件定位:
① 重置文件头:void rewind(FILE *stream); //使文件位置重新回到文件开头
② 文件随机读写:int fseek(FILE *Stream, long offset, int origin); //使文件位置移到任意位置。offset表示偏移量,以字节为 单位,正值表示向后,负值表示向前,参数origin 表示相对初始点:
#define SEEK_SET 0 //移动时相对初始点在文件开头
#define SEEK_CUR 1 //文件当前位置
#define SEEK_END 2 //文件末尾
③ 文件当前位置: long ftell(FILE *stream)
38. 算法
(1)分治法:将规模为n的问题分解为k个规模较小的子问题,这些子问题相互独立且与原问题相同,然后递归解这些子问题, 然后将各子问题的解合并得到原问题的解。
(2)动态规划:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解,与分治法不同的 是,适合用动态规划求解的问题经分解得到的子问题不是互相独立的。动态规划使用一张表来记录所有已解的子 问题的答案。
基本步骤:找出最优解的性质,并刻画其结构特征,递归的定义最优值,以自底向上的方式计算出最优值,根据 计算最优值时得到的信息构造一个最优解。
(3)贪心算法:在求最优解问题的过程中,依据某种贪心标准,从问题的初始状态出发,直接去求每一步的最优解,通过若干 次的贪心选择,最终得出整个问题的最优解。
(4)回溯法:针对所给问题定义问题的解空间,根据深度优先的策略,从根节点出发搜索解空间树,算法搜索至解空间树的任一节点时,首先判断该结点是否肯定不包含问题的解,如果肯定不包含,则跳过以该结点为根的子树的系统搜索,逐层向其祖先结点回溯。否则进入该子树,继续按深度优先策略进行搜索。
39. 高级编程技术:
(1)专业函数库的配置
(2)界面编程:图形用户界面GUI #include<windows.h>
(3)图形编程:建模、渲染、动画、人机交互、虚拟现实、可视化、图像处理、三维扫描、矢量图形等
(4)多媒体编程:高层的MCI(媒体控制接口,直接控制媒体设备硬件)和底层的MMAPI(间接控制目标设备,如WAV)
(5)网络编程:winsock、TCP编程、UDP编程
(6)数据库编程:ODBC