第二章 基本数据类型与表达式
指数形式:指数E(e)之前必须有数字,后面的指数必须为整数
要求%运算左右两数必须为整形数据
所有的字符串都以
‘\0’
字符结束
变量:代表计算机内存中的某一存储空间,这个存储空间可以存放不同的数据
变量的定义:
必须先定义后使用
同一程序中变量不允许被重复定义
定义变量时,变量不能连读赋初值
函数内部——>局部变量
所有函数外部——>全局变量
函数的参数定义——>形式参数
当变量被定义成某一类型,编译时将据此为其分配相应长度的存储单元
编译:只能发现语法错误,不能发现算法错误
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i=8,j=10;
printf("%d,%d,%d,%d\n",i,j,++i,j++); // printf函数的参数一般从右往左计算!
system("pause");
return 0;
}
输出: // 9 10 9 10
{
int a=1,b=2,c=3;
//printf("%d\n",b==c); 【 0 】
printf("%d,%d,%d,%d\n",a=b=c,a=b==c,a==(b=c),a==(b==c));
system("pause");
return 0;
}
输出: 3 1 0 0
第三章 顺序结构
字符输入输出函数
getchar():
输入一个字符,函数的返回值是该字符的ASCII编码值
putchar():
函数的参数可以是字符常量、字符型变量、整型变量,即将一个整型数作为ASCII编码,输出相应的字符
每条语句只能输入输出一个字符;
执行
getchar()
函数输入字符时,输入字符后需要按 Enter 键,然后程序才会响应输入,继续执行后续语句;
getchar()
函数也将 Enter 键作为一个回车符读入,因此,在用getchar()
函数连续输入两个字符时要注意回车符;
getch
和putch
的作用与getchar
和putchar
相似,getch
不显示键盘输入的字符。
格式输入输出函数
scanf():
scanf("%d,%f",&a,&b);
// 输入一个整型数和一个浮点数之间要输入一个逗号 【 , 】
用 scanf()
函数输入实数,格式说明符为 “%f”
,但不允许规定精度
printf():
%e:以标准宽度输出十进制实数(13位):尾数的整数部分为非零数字占1位,小数点占1位,小数占6位,e占1位,指数正(负)号占1位,指数占3位
1000.7654321 ——> 1.000765e+003
%d(% i ):以带符号的十进制形式输出整数
%u:以十进制形式输出 unsigned 型数据
%o,%x 输出的八进制,十六进制 整数 不带符号
补码的表示方法是:
正数的补码就是其本身
负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+1] = [00000001]原 = [00000001]反 = [00000001]补
[-1] = [10000001]原 = [11111110]反 = [11111111]补
第四章 选择结构
1.0/3.0*3.0 == 1.0 【x】 1.0/3.0 得到的值用有限位保存,是近似值 【浮点数是用近似值保存的!】
一般判断两个浮点数是否相等,采用以下形式:(判断绝对值)
fabs(1 - 1.0/3.0 * 3.0)<1e-5 // 1e-5 :预先指定的精度
abs
:整数取绝对值
fabs
:浮点数取绝对值
else 总是与其上面最近的未匹配的 if 配对!
第五章 循环结构
break: 跳出所在的循环体,转向执行该循环体后面的语句
continue: 跳过循环体中 continue
语句后面尚未执行的循环体语句,继续进行下一次循环
goto: 使流程转移到相应标号所在的语句,并从该语句继续执行
goto 标号;
标号:语句
第六章 函数与编译预处理
函数的定义:用来建立和实现函数的功能,必须有函数体。
类型名 函数名(参数类型说明及列表)
{
局部变量说明
语句序列
}
函数的声明: 仅给出函数的类型,函数名及其参数的类型。
类型名 函数名(参数类型说明列表);
// 分号;
// 形参名可以省略,但类型,次序和数目必须一致!
先定义,后引用;
如果对某函数的调用出现在该函数定义之前,必须用说明语句对函数进行声明。
函数的调用:
有参函数调用:
函数名(实参表达式1,实参表达式2,...)
无参函数调用:
函数名()
参数的传递:
形参:是变量,函数被调用时作为局部变量被分配内存。当函数执行完毕返回时,形参占用的内存空间又被释放。
实参:可以是变量,常量和表达式,但必须有确定的值。
形参和实参的类型必须一致。
单向的值的传递,即将实参的值传递给形参,形参的值在函数中不论怎么改变,都不会影响实参。
#include <stdio.h>
int fun1(int x, int y)
{
int i;
for(i = 1;i<=y;i++)
x += x;
return x;
}
void main()
{
int a,b,s;
scanf("%d%d",&a,&b);
s= fun1(a,b);
printf("a = %d,s = %d\n",a,s);
}
输入:3 4
输出:a=3,s=48 //函数执行4次“x += x”,x的值为48,并不影响主函数中的变量a
变量的作用域:
-
局部变量:在函数内定义的函数,其作用域仅限于函数内。
-
全局变量:定义在函数之外的变量,即从定义之处起,可以在本文件的所有函数中使用。
(1)全局变量的声明符为
extern
,全局变量定义只能定义一次,全局变量声明可以在多个函数中出现;(2)同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。
(3)全局变量使得函数的独立性降低,尽量不要使用全局变量!
<!--即外部变量,其作用域是这个源程序文件--> int a,b; 等效于: extern int a,b;
-
外部变量的使用:一个文件中的全局变量可以在另一个文件中作为外部变量使用。
extern 类型说明符 外部变量名; file1.c int students; file2.c //写在文件头部,可在该文件的任何函数内对 students 进行操作 extern int students; // 【该文件中不会为外部变量分配内存!】 fun1() { students = 30;......} fun2() { students += 20;......} /* fun1() { extern int students; ...... }*/
#include <stdio.h> int vs(int l,int w) // w 是形参,局部变量 { extern int h; // 外部变量声明 int v; v=l * w * h; return v; } void main() { extern int w,h; // 外部变量声明 int l=5; printf(" v = %d",vs(l,w)); } int l =3,w = 4,h = 5; // 定义全局变量 输出:v = 100
变量的存储方式:
静态存储:在变量定义时就分定存储单元并一直保持不变,直至整个程序结束;
动态存储:在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放。如果一个函数被多次调用时,则会多次分配和释放形参变量的存储单元。
-
自动变量(
auto
): (默认),动态存储方式,被分配在内存的动态存储区(1)作用域仅限于定义该变量的结构内,即在函数中定义的自动变量只在该函数内有效;在复合语句中定义的只在该复合语句中有效;
(2)只有定义该变量的函数被调用时才给她分配存储单元,开始它的生存期;
(3)由于自动变量的作用域和生存期都局限于定义它的个体内(函数或复合语句内),所以函数内定义的自动变量也可与该函数内部的复合语句中定义的自 动变量同名。
-
静态变量(
static
):静态存储方式,被分配在内存的静态存储区(1)静态局部变量:
a. 在函数内定义,但它的生存期为整个程序;
b. 生存期虽为整个程序,但其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量,退出该函数后,尽管该变量继续存在,但不能在使用
#include <stdio.h> void main() { int i; void f(); //函数声明 for(i = 1;i <= 5;i++) f(); //函数调用 } void f() //函数定义 { int j = 0; //j为自动变量 ++j; printf("%d\n",j); } 输出:1 // 输出5次 static int j = 0; //j为静态局部变量 输出:(调用后保留其值并在下一次调用时继续使用) 1 2 3 4 5
(2)静态全局变量:
全局变量:作用域可以扩展到整个程序;
静态全局变量:作用域局限于一个源文件内,只能为该源文件内的函数使用,避免在其他源文件中引用,防止出现错误。
file1.c 中定义了全局静态变量 static int students; file2.c 中不能使用说明外部变量 extern int students; 【 X 】
-
寄存器变量(
register
):自动变量和静态变量是内存变量,是由编译程序 【在内存中】 分配单元;
寄存器是CPU中一个很小的临时存储器,一般用以存储一些反复被加工的数据;
通过变量访问寄存器,寄存器的存取速度比主存快;
寄存器变量只限于整型,字符型和指针型的局部变量;
是动态变量,而且数目有限,一般仅允许说明两个寄存器变量。
宏定义: 在对源程序中的其他成分正式编译之前进行的
- 无参宏定义:
a .宏定义不是说明或语句,在行末不加分号;
b. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束;
c. 如要终止其作用域可使用 ‘’
#undef 标识符
‘’ 命令;d. 用引号括起来的宏名,预处理程序不对其进行宏代换;
e. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名;
#define PI 3.14
#define P PI*a*b // e. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名;
#undef PI // c. 如要终止其作用域可使用 ‘’ #undef 标识符 ‘’ 命令;
#define HELLO 100
.....
printf("HELLO"); // d. 把 HELLO 作为字符串处理,输出 HELLO
- 带参数的宏定义:
a. 宏名和形参表之间不能有空格,有空格即为无参宏定义;
b. 带参宏定义中的参数不是变量,只是符号,不存在值传递的问题;
c. 宏定义中的形参是标识符,宏调用中的实参可以是表达式。
函数调用时要把实参表达式的值求出来再赋予形参,而宏代换中对实参表达式不做计算直接照原样代换!
带参宏定义:【宏名和形参表之间不能有空格!】
#define 宏名(形参表)字符
// 有空格即为无参宏定义!
#define L(x)(x * x + 2 * x + x)
带参宏调用:
宏名(实参表)
y = L(5);
z = 1 / L(a + 5);
#include <stdio.h>
#include <stdlib.h>
#define SQR(y)((y)*(y))
int sqr(int y)
{
return((y)*(y));
}
void main()
{
int i,j;
printf("sqr\ti\tSQR\tj\n");
for(i=1,j=1;i<=5;)
{
printf("%d\t",sqr(i++)); //把实参i值传给形参y后自增1
printf("%d\t",i);
printf("%d\t",SQR(j++)); //代换为(j++)*(j++),一次调用j会发生2次自增
printf("%d\n",j);
}
system("pause");
}
输出:
sqr i SQR j
1 2 1 3
4 3 9 5
9 4 25 7
16 5 49 9
25 6 81 11
条件编译:
- 功能:如果标识符已被
#define
命令定义过,则对程序段 1 进行编译,否则对程序段 2 进行编译
#ifdef 标识符
程序段1
#else
程序段2
#endif
- 功能:与 1 相反
#ifndef 标识符
程序段1
#else
程序段2
#endif
- 功能:如果常量表达式的值位真,则对程序段 1 进行编译,否则对程序段 2 进行编译
#if 常量表达式
程序段1
#else
程序段2
#endif
第七章 数组
对数组初始化:用花括号括起来,各值以逗号分隔。行下标可以省略。
static int a[3][2] = {0,1,2,3,4,5};
static int a[3][2] = {{0,1},{2,3},{4,5}};
static int a[3][2] = {0,1},{2,3},{4,5}; 【 X ,总的花括号不能省略!】
字符串的存储:
a. 字符串结束标志
“ \0 ”
仅用于判断字符串是否结束,输出字符串时不会输出;b. 初始化一个一维字符数组时,可以省略花括号。
char s[8] = “program”;
c. 不能直接将字符串赋值给字符数组。
s = “program”;
【 x 】strcpy(name, "shu_du");
字符串的输入:
-
使用
scanf
输入字符串:a. 最后以回车或空格作为结束输入,因此无法将包含有空格的字符串输入到字符数组中;
b. 系统将自动在字符串末尾补上字符串结束标识符
“ \0 ”
.
输入:How do you do ?
输出:How\0 // 空格作为结束标志!
-
使用
gets
输入字符串:a. 最后以回车作为结束输入;
b. 自动在字符串末尾补上字符串结束标识符
“ \0 ”
。
char s[20];
gets(s);
puts(s);
输入:How do you do ?
输出:How do you do ?\0
字符串的输出:
-
使用
printf
输出字符串:不会自动换行!
可以同时输出多个字符串。
-
使用
puts
输出 【一个】字符串:将
“ \0 ”
转换成换行符,因此,输出一行时,不必另加换行符“ \0 ”
!一次只能输出一个字符串。
函数原型:int put(char *str); 调用格式:puts(str); printf("%s,%s",s1,s2); 【 √ 】 puts(s1,s2); 【 X 】
#include <stdio.h>
void main()
{
static char str[20] = { "How do you do ?" };
int k;
printf("printf输出:\n");
printf("%s\n", str);
for(k = 0; str[k] != '\0'; k++)
printf("%c", str[k]);
printf("\n\n**************\n\n");
printf("puts输出:\n");
puts(str);
puts("Fine.Thank you.");
}
输出:
printf输出:
How do you do ?
How do you do ?
**************
puts输出:
How do you do ?
Fine.Thank you.
字符串处理函数:(库:string.h
)
-
复制函数(
strcpy
):将字符串str2
连同“\0”
复制到字符数组str1
中,str2
的值不变a.
str1
的长度应不小于str2
的长度;b.
srr1
必须写成数组名形式,而str2
可以是字符串常量,也可以是字符数组名形式; -
连接函数(
strcat
):将字符串str2
连同“\0”
连接到字符数组str1
的最后一个非“\0”
字符后面,str2
的值不变 -
比较函数(
strcmp
):不是字符串长度的比较,两个字符串从左往右逐个字符比较 ASCII 码值大小,直到出现不同字符或遇到“\0”
为止若 ``str1 = str2`,函数返回值为 0;
若
str1 > str2
,函数返回值为1;若
str1 < str2
,函数返回值为 -1; -
长度函数(
strlen
):求字符串的实际长度(不包括“\0”
),由函数值返回
#include <stdio.h>
#include <string.h>
void main()
{
static char str1[30] = {"I am a "},str2[10] = {"student."};
strcat(str1,str2);
puts(str1);
puts(str2);
//strcpy(str1,str2);
strcpy(str1,"I am a ");
puts(str1);
puts(str2);
}
输出:
I am a student.
student.
I am a
student.
数组作为函数的参数:
-
数组元素作为函数实参:与变量相同,是单向的值的传递
-
数组名作为函数参数:
a. 形参与实参都应使用数组名,分别在被调函数与主调函数中说明数组类型,并且类型和维数相同;
b. 是地址传递,即实参数组的首地址传递给形参数组,而不是将实参数组的每个元素送给形参的各数组元素;
c. 数组名作为函数参数时是把数组的起始地址传给了形参数组,这样形参与实参数组共用同一段内存单元。这种传递方式,使得形参中数组元素的变化影 响实参数组元素的值同时变化;
#include <stdio.h>
#include <stdlib.h>
#define N 7
void main()
{
int b[N][N],i,j;
printf("杨辉三角形如下 : \n");
void setdata(int a[][N],int); // d:对函数声明时,形参名可以省略 int n
setdata(b,N); // c:生成的杨辉三角形各值保存在数组 b 中,而 b 是 a 的实参,b 与 a 有相同的值
for(i=0;i<N;i++)
{
for(j=0;j<=i;j++)
printf("%6d",b[i][j]);
printf("\n");
}
system("pause");
}
void setdata(int a[][N],int n) // 实参数组的首地址传递给形参数组
{
int i,j;
for(i = 0;i<n;i++)
{
a[i][i] = 1;
a[i][0] = 1;
}
for(i=2;i<n;i++)
for(j=1;j<i;j++)
a[i][j] = a[i-1][j-1] + a[i-1][j];
}
第八章 指针
指针的赋值运算:
a. 相同类型的指针变量之间可以相互赋值;
b. 给指针变量赋空值:
pa = NULL;
pa = 0;
pa = ‘\0’;
注意:指针变量虽然可以赋值 0 ,但却不能把其他的常量地址赋给指针!
pa = &a;
【√】pa = 4000;
【 X 】c. 全局指针变量,在定义时若未被初始化,则编译系统会自动初始化为空指针 0 ;
局部指针变量,不会被自动初始化,因此指向不明确。
指针的算术运算:
int a = 2,*p;
p = &a; // 4000
//指针变量的值(地址)加(减)“ n x sizeof(指针类型)”
p = p + 2; // 4000 + 2 x sizeof(int) = 4000 + 2x2 = 4004
多级指针:
p , pp 都是指针变量,但 pp 只能指向指针变量而不能指向普通变量!
int a,*p,**p;
a = 20;
p = &a;
pp = &p;
不能将 a 这个整型变量的地址赋给 pp ,而只能将一个整形指针变量的地址赋给二级指针!
pp = &a; 【 X 】
指针作为函数参数:
- 函数形参为指针变量,用指针变量作为实参:
int *pa,*pb;
void swap(int *p1,int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void main()
{
......
pa = &a;
pb = &b;
swap(pa,pb); // 指针变量作为实参
......
}
- 函数形参为指针变量,用变量地址作为实参:
int *pa,*pb;
void swap(int *p1,int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
void main()
{
......
swap(&a,&b); // 变量地址作为实参
......
}
指针变量的值也遵循单向传递的原则,指针形参值的改变不会影响指针实参的值!
void swap(int *p1,int *p2)
{
int *temp;
temp = p1; // 形参值的改变
p1 = p2;
p2 = temp;
}
未对指针变量 p 赋初值,p 的值不确定,即 p 指向一个未知的存储单元,不能对 p 引用!
void swap(int *p1,int *p2)
{
int *p; // 未对指针变量 p 赋初值
*p = *p1; // 不能对 p 引用
*p1 = *p2;
*p2 = *p;
}
指针函数: 函数的返回值为指针的函数*
类型标识符 *函数名(形式参数表)
int *fun(int a,int b)
{
函数语句体
//有返回指针或地址的语句!返回值为一个 int 型指针。
return(&变量名); // return(指针变量);
}
指针函数的返回值一定得是地址,并且返回值的类型要与函数类型一致!
#include <stdio.h>
#include <stdlib.h>
void main()
{
int a,b,*p;
scanf("%d,%d",&a,&b);
int *min(int x,int y); // 对函数声明时,形参名可以省略
p = min(a,b);
printf("min = %d\n",*p);
int *minp(int *x,int *y);
p = minp(&a,&b);
printf("minp = %d\n",*p);
system("pause");
}
int *min(int x,int y)
{
if(x < y)
return(&x); // x 的地址作为指针函数的返回值
else
return(&y);
}
int *minp(int *x,int *y)
{
int *q;
q = *x < *y?x:y;
return(q); // 指针变量 q 作为指针函数的返回值
}
指向函数的指针:
【函数指针作为函数参数的意义:当函数指针每次指向不同的函数时,执行不同的函数来完成不同的功能。】
类型标识符 (*指针变量名)()
int (*p)(); // p 指向一个返回整型值的函数
float (*q)(); // q 指向一个返回浮点型值的函数
函数引用:
赋予地址值才能引用!函数名代表了函数的入口地址,因此无需考虑函数参数
int (*p)();
void max(int x,int y);
{
......
}
p = max; // p 指向函数 max ,即是将 max 的入口地址赋给指针变量 p
p = max(m,n) 【 X 】 因为 max(m,n) 是函数调用,其返回值为整型值,不是指针,故不能赋给指针变量 p
区别:
int (*p)(); // p 为一个指针变量(是指向函数的)
int *p(); // p 是一个函数,其返回值为指针
函数调用:
-
直接调用:函数名调用法;
-
间接调用:使用指针 p 调用函数;
( *指针变量 )( 实参表 )
a 为一个整型变量: 实参一定要和指针所指函数的形参类型一致 a = max(m,n); //函数名调用 a = (*p)(m,n); //使用指针 p 调用
#include <stdio.h>
#include <stdlib.h>
#define M 10
float max(float a[],int n)
{
int k;
float s = a[0];
for(k = 0;k<n;k++)
{
if(s<a[k])
s = a[k];
}
return s;
}
void main()
{
float sumf,sump;
float a[M] = {11,2,-3,4,5,5,69,7,80};
float (*p)(); //定义指向函数的指针 p
p = max; //函数名(函数入口地址)赋给指针
sump = (*p)(a,M); //用指针方式调用函数
printf("sump = %.2f\n",sump);
sumf = max(a,M); //用函数名调用函数
printf("sumf = %.2f\n",sumf);
}
指向函数的指针的使用步骤:
1. 定义一个指向函数的指针变量;
float (*p)();
2. 为函数指针赋值;
p = 函数名; //赋值时只需要给出函数名,不带参数
3. 通过函数指针调用函数;
s = (*p)(实参);
区别:
函数指针:
a. 指向内存的程序代码区;
b. 主要作用体现在函数间传递函数,这种传递是传递函数执行代码的入口地址(所以对其进行算术运算是没有意义的),即函数的调用控制;
数据类型指针:指向内存的数据区。
指针与数组:
-
指向一维数组的指针:
数组名 a 代表数组首地址,是一个常量指针,其值在数组定义时已确定,不能改变,不能进行
a++
,a = a + 1
等类似的操作;
数组名 a 作为形参时,在编译时未分配固定的内存单元,因而可以进行
a++
,a = a + 1
等类似的操作;p 是指针变量,其值可以改变,当赋给给 p 不同元素的地址值时,指向不同元素:
p++
,p = p + 2
;
赋初始值:
int a[5] = {0,2,4,6,8};
int *p = a // int *p = &a[0]
如果 p 指向数组的首地址,表示第 i 个元素:
a[i] *(a+i)
p[i] *(p+i)
#include <stdio.h>
void main()
{
static int a[5] = { 1,3,5,7,9 };
int i, *p;
for(i = 0; i < 5; i++)
printf("%4d", a[i]); // a[i]
putchar('\n');
for(i = 0; i < 5; i++)
printf("%4d", *(a + i)); // *(a+i)
putchar('\n');
for(p = a; p < a + 5; p++)
printf("%4d", *p); // *p
putchar('\n');
for(p = a, i = 0; i < 5; i++)
printf("%4d", p[i]); // p[i]
putchar('\n');
for(p = a, i = 0; i < 5; i++)
printf("%4d", *(p + i)); // *(p+i)
putchar('\n');
}
输出:
1 3 5 7 9
1 3 5 7 9
1 3 5 7 9
1 3 5 7 9
1 3 5 7 9
利用指针变量访问数组元素时要注意指针变量的当前值,特别是在循环控制结构中!
循环执行完后,p 指向数组 a 以后的整型单元,若使指针 p 输出数组a的各元素,必须先将p重新指向第一个元素,即:p = a
1. * p++ 相当于 * (p++) : 先获得 p 指向变量的值,再执行 p = p + 1; ( *与 ++的优先级相同,结合方向从右往左);
2. 若 p = a
* (p++):先输出a[0],再让 P 指向a[1];
* (++p):先使 P 指向a[1],再输出 p 所指的a[1];
3. (*p)++:将p所指的变量值加1.
-
二维数组与多维数组的指针表示法
a. 二维数组的地址:
元素 地址 a[i][j] &a[i][j] *(a[i]+j) a[i]+j *(*(a+i)+j) *(a+i)+j **a 等价于 *(*(a+0)+0) 若a是一维数组,则a[i]代表数组a的第i个元素,它占有是实际的物理存储单元; 若a是二维数组,则a[i]代表一维数组名,只是一个地址,不是具体元素,故也不占用存储单元。 对于二维数组 a : 1. a+i, &a[i] 指向第 i 行(第 i 行首地址),**是二级指针**。a+i 是指向行,而不指向具体单元; 2. *(a+i),a[i],a[i][0] 指向第 i 行第 0 列元素的地址,对象是一个整型元素,是一级指针。
#include <stdio.h>
void main()
{
static int a[2][3]={{0,1,2},{3,4,5}};
int k,j,*p;
for(j=0;j<2;j++)
{
for(k=0;k<3;k++)
printf("%5d",a[j][k]); // a[j]是 j 行首地址,a[j][k] 是 j 行 k 列元素的地址
putchar('\n');
}
putchar('\n');
for(j=0;j<2;j++)
{
for(k=0;k<3;k++)
printf("%5d",*(a[j]+k)); // a[j]是 j 行首地址,a[j]+k 是 j 行 k 列元素的地址
putchar('\n');
}
putchar('\n');
for(j=0;j<2;j++)
{
for(k=0;k<3;k++)
printf("%5d",*(*(a+j)+k)); // *(a+j)是 j 行首地址,*(a+j)+k 是 j 行 k 列元素的地址
putchar('\n');
}
putchar('\n');
for(p=a[0],j=0;j<2;j++) // p 指向数组的第一个元素
{
for(k=0;k<3;k++)
printf("%5d",*(p++)); // 输出 p 所指示的元素
putchar('\n');
}
}
b. 指向二维数组的指针变量
(1)指向数组元素的指针变量
printf("%5d", *(*(a+i)+j));
(2)行指针:指向一维数组(二维数组的一行)的指针,是二级指针
类型标识符 (*指针变量名)[元素个数]
int (*p)[5];
int a[3][5];
p=&a[0]; // p=a p=a+0 p指向第 0 行
p+1; // 指向下一行a[1], p 的值应以一行占用存储字节数为单位进行输出
- 指针与字符串:
指针初始化:
char *p = "comuter"; 【 √ 】 将字符串的首地址赋给指针变量 p ,而不是将具体的字符串常量赋给 p
printf("%s",p);
char a[10];
char *p;
a="computer"; 【 X 】 数组名 a 是一个常量指针,不能对其赋值
- 字符数组与指针数组:
字符数组:各字符串存在一片连续的存储单元中,因为字符串长度不同,各行字符间并不是连续存储的,这样会浪费存储空间;
指针数组:各个字符串并不是连续存储的,不占用多余的内存空间。
第九章 结构体数据类型与链表
结构体类型定义:
在编译时,对类型是不分配存储空间的,只对变量分配存储空间!
成员也可以是结构体变量,即一个结构体类型中的某些成员又是其他结构体类型。但嵌套不能包含其自身,即不能自己定义自己!
成员名可以与程序中的其他变量名相同,两者不代表同一对象!
struct 结构体类型名
{
//成员说明列表
类型符 成员名;
};
结构体变量的使用:
结构体变量名.成员名
指针变量名->成员名
初始化: 对自动结构体变量不能在定义时赋初值,只能在函数执行时用赋值语句对各成员分别赋值!
输入输出: 不允许把一个结构体变量作为一个整体进行输入输出!
结构体类型数组:
- 访问结构体数组元素的成员:
结构体数组名[元素下标].结构体成员名
struct
{
char name[20];
...
}parts[200];
part[10].name = "zhang_fei";
-
结构体类型数组初始化:
只能对定义为外部的或静态的数组进行初始化。初始化时要将每个元素的数据分别用花括号括起来!
struct student { char name[20]; long num; int age; char sex; float score; }student[3] = { {"yang_hu",01,18,'M',90}, {"wang_qi",02,19,'M',91}, {"qing_hu",03,20,'F',92} }; /* strcpy(name, "shu_du"); 若赋初值的数据组的个数与所定义的数组元素相等,则数组元素个数可以省略不写; 对其他元素未赋初值时,系统将对数值型成员赋以 0 ,对字符型数据赋以空字符串。 */
-
不能把结构体数组元素作为一个整体直接进行输入输出,只能以单个成员对象进行输入输出!
scanf("%s", student[0].name); // 字符数组名本身代表地址,不应该写成 &student[0].name
printf("%ld", student[0].num);
指向结构体变量的指针:
struct 类型名 *指针变量名;
指针变量->结构体成员名;
(*指针变量).结构体成员名
struct student
{ ... }stu;
//结构体变量作为函数参数
void list(stu)
struct student stu;
{ ... }
void main()
{
list(stu);
}
//指向结构体变量的指针作为函数参数
void list(struct student *p) { ... }
void main()
{
list(&stu);
}
内存动态管理:
-
malloc( )
函数:在内存开辟指定大小的存储空间,并将此存储空间的起始地址作为函数值带回;
若将指针值赋给其它类型的指针变量,应当进行显示的转换(强制类型转换);
p = ( long * )malloc( 8 )
:开辟一个长度为 8 个字节的内存空间,将此空间的起始地址赋给一个指向 long 型的指针变量 p ,进行显示转换 -
calloc( num , size )
函数:分配
num
个大小为size
字节的空间,此函数返回值为该空间的首地址;calloc( 20 , 30 )
:开辟 20 个每个大小为 30 字节的空间,即总长为 600 字节 -
free( 指针变量 )
函数:
p = ( long * )malloc(8);
...
free( p ); // 指针变量 p 值不能是任意的地址值,只能是由在程序中执行过的 malloc 或 calloc 函数所返回的地址
-
realloc( p , size)
函数:将 p 指向的存储区(原先用
malloc
函数分配的)的大小改为size
个字节;函数返回值是一个指针。
链表:
链表的节点是结构体变量:
struct student
{
long num;
int score;
struct student *next; //引用类型名定义自己的成员变量的方法,只允许定义指针时使用!
};
-
建立链表:
(1)插表头(先进后出):新产生的节点作为新的表头插入链表
1. head = NULL; // 表头指向空,表示链表为空 2. 产生新节点,地址赋给指针变量 p ; 3. p->next = head; head = p ; // 插表头操作,将 head 指示的链表接在新节点之后 4. 循环执行 2 可继续。
(2)链表尾(先进先出):新产生的节点接到链表的表尾
1. head = last = NULL; // 表头指向空,表示链表为空,last 是表尾指针 2. p->next = NULL; // 产生新节点,地址赋给指针变量 p,新节点作为表尾 3. 若 head 为 NULL,则: head = p; // 新节点作为表头,这时链表只有一个节点 否则: last = p; // 链表操作 4. last = p; // 表尾指针 last 指向新节点 5. 循环执行 2 ,可继续建立新节点
-
插入链表:
1. 指针 head 指向链表的节点,p0 指向待插入节点,p1 和 p2 一前一后指示插入点; 2. 最初 p1 = head; 3. 移动指针 p2 = p1, p1 = p1->next 直到找到插入点; 4. 插入节点 p2->next = p0, p0->next = p1
-
删除链表:
1. p1 = head; // p1 指向删除节点,p2 指向前一个节点 2. 当 p1 指向的节点不是满足删除条件的节点,且没有到表尾时: p2 = p1; p1 = p1->next; // 移动指针 p1 ,继续查找 3. 如果 p1 != NULL 如果 p1 == head 删除的是头节点 则 head = head->next; // 删除头节点 else p2->next = p1->next; // 删除 p1 指向的节点 4. free( p1 ); // 释放删除节点的内存
-
输出链表:
1. p = head; // p 指向第一个节点 2. p = p->next; // 指向下一个节点
-
查找链表:
1. p1 = head; // 从链表的头指针所指的第一个节点出发,顺序查找 2. p2 = p1; p1 = p1->next; // 移动指针 3. 未找到,或查找至链表结尾,查找结果为 NULL;
共用体:
union data // data 共用体类型名
{
//成员说明列表
int i;
char ch;
float f;
}a = { 120 };
union data b,c;
引用:一个共用体变量不是同时存放多个成员的值,只能存放其中的一个成员的值,就是最后赋给它的值!
a.ch = 'A';
a.f = 3.14;
共用体变量中最后的值是 3.14
特点:
共用体变量所占用的内存单元的字节数不是成员的自己的字节数之和,而是等于成员中最长字节的成员所占内存空间的字节数;
//共用体变量 a 的 3 个成员共享 4 个字节的内存空间;
共用体变量 a 中不能同时存在 3 个成员,只是可以根据需要用 a 存放一个整数型,或一个字符型,或一个浮点型(引用);
可以对共用体变量进行初始化,但在花括号中只能给出第一个成员的初值(引用);
共用体一般不单独使用,通常作为结构体的成员,这样结构体可根据不同情况存放不同类型和数据。
枚举类型:枚举类型变量常用于循环控制变量,枚举常量用于多路选择控制情况
// 枚举元素或枚举常量,命名规则与标识符相同
enum 枚举类型名{ 标识符1, 标识符2 ... 标识符n }
定义:
enum colorname{ red, yellow, blue, white, black };
enum colorname color; // 变量 color 只能是枚举元素
赋值:
color = red; 【 √ 】
color = green; 【 X 】
说明:
-
枚举元素不是变量,不能改变其值;
red = 8; yellow = 9; 【 X 】
-
枚举元素作为常量,它们是有值的。从第一个元素开始,系统自动赋给的值分别为 0,1,2,3 …
printf( "%d", blue ); // 2
-
在定义时进行初始化;
enum colorname{ red=3, yellow, blue, white=8, black }; //yellow 为 4 ,blue 为 5 ,white=8,black 为9
-
枚举常量可以进行比较,是按所代表的整数进行比较的;
if(color != black)printf("it is not black"); if(color > white)printf("it is black!");
-
枚举常量不是字符串,不能用如下方法输出;
printf("%s", blue); 【 X 】 color = red; if(color == red)printf("red");
第十章 文件
文件:
字符文件:以字符形式存放,每个字符用一个 ASCII 代码表示。//文本文件,正文文件
二进制文件:以二进制代码形式存放,以数据在内存中存储形式的原样存于磁盘上。
-
从磁盘文件读数据时:
应用程序并不直接从磁盘文件读取数据,而是先由系统将一批数据从磁盘取入内存输入缓冲区,再由应用程序的读操作从缓冲区依次将数据送给程序中的接收变量,供程序处理。
-
向磁盘文件写入数据时:
将程序中有关变量或表达式的值送到缓冲区中,待缓冲区装满后,才由系统将缓冲区的数据一次写入磁盘文件中。
文件指针:
FILE *fp;
fp = fopen( "文件名", "文件操作方式");
FILE *fp;
if((fp = fopen("E:\typora(notes)\c笔记.md", "r")) == NULL)
{
printf("FILE cannot be opened !\n");
exit(1); // 函数参数为非零值,表示出错返回,exit(0) 表示程序正常返回
}
else
{
printf("EILE opened for reading !\n");
......
}
//exit 函数返回操作系统,并关闭所有打开的文件
关闭标准文件:
程序对文件的读写操作完成后,必须关闭文件。对打开的磁盘文件进行写入操作时,若文件缓冲区的空间未被写入的内容填满,这些内容将不会自动写入打开的文件中,从而导致内容丢失。只有对打开的文件进行关闭操作时,停留在文件缓冲区的内容才能写到磁盘文件中,从而保证了文件的完整性。
fclose(fp);
表示该函数将关闭
fp
指向的文件,并返回一个整数值。若成功关闭文件,则返回 0 ,否则返回一个非零值;
fcloseall();
同时关闭程序中已打开的多个文件(5个标准设备文件除外),将各文件缓冲区未装满的内容写到相应的文件中,并释放缓冲区,返回关闭文件数目;
若程序已打开3个文件
n = fcloseall();
// 3
文件操作状态检测:
-
判断文件是否结束函数:
feof(文件指针)
feop(fp);
// 表示fp
指向的文件到达文件尾时,函数feof(fp)
的值为1,否则为 0 ; -
判断文件操作知否有错函数:
ferror(文件指针)
ferror(fp);
// 如果函数ferror(fp)
返回值为 0,表示未出错,非零值,表示出错。
标准文件读写操作:
-
从文件读取数据到内存:
ch = fgetc(fp);
// 从指针fp
所指的文件读取一个字符到内存若执行
fgetc()
函数时遇到文件结束符EOF
,则函数返回一个值 -1 给 chfgets(str,n,fp);
// 从指针fp
指定的文件中读取 n-1 个字符,送至由str
内存区遇到
"\n"
也将作为一个字符送入字符数组中;
在读入字符串之后会自动添加一个字符串结束符"\0"
,因此送入字符数组中的字符串(包括"\0"
)最多为 n 个字节;
执行完毕后,返回一个指向该字符串的指针,即字符数组的首地址。若读到文件尾或出错,则返回一个空值 NULL 。 -
从内存输出数据到文件:
fputc(ch,fp);
// 把内存中 ch 的数据,输出到fp
所指的文件中执行成功则返回被输出的字符,否则返回
EOF
fputs(str,fp);
// 把内存中str
的数据,输出到fp
指定的文件中“ \0 ”
并不输出,也不自动加换行符 -
格式化读写函数:
fscanf(文件指针,格式控制符,输入项列表);
// 由fp
指定的文件输入fprintf(文件指针,格式控制符,输入项列表);
// 由fp
指定的文件输出标识符 数字 代表的起始位置 SEEK_SET 0 文件开始 SEEK_END 1 文件末尾 SEEK_CUR 2 文件当前位置
-
文件指针定位函数:
fseek(fp,10L,SEEK_SET);
//fseek(fp,10L,0);
把文件指针从文件开头移到第 10 个字节处,若调用成功,返回值为 0 ,否则返回一个非零值;
rewind(fp);
使文件位置指针回到起始位置。移动成功时,返回值为 0 ,否则返回一个非零值;
ftell(fp);
反绕函数。返回一个整数,获得文件位置指针当前指针位置(离开文件起点的偏移量,偏移的字节数);
若函数调用出错(如文件不存在),则返回 -1。
-
文件随机读写函数:进行读写操作时,必须采用二进制
fread(buffer,size,count,fp);
buffer
:表示数据库指针,内存块的首地址,输入数据存入到buffer所对应的块儿;
size
:每个数据块的字节数,输入输出多少个字节;
count
:数据库的个数;
fp
:文件指针。fread(buffer,50,64,fp); 从指针 fp 指向的文件中,读取 64 个数据项,每个数据项长度为 50 个字节,存入到 buffer 数组(内存缓冲区)中。
fwrite(buffer,size,count,fp);
buffer
:输出数据的起始位置。
fwrite(buffer,50,64,fp);
从数组名 buffer 所代表的数组起始地址开始,每次输出长度为 50 个字节的数据项,共输出 64 个数据项,将它们写入到指针 fp 指向的磁盘文件中。
若写入成功,函数返回值为 64
void main()
{
FILE *fp; // 定义文件指针
char ch;
if((fp = fopen("E:\\Visual Studio 2019\\hjt1.txt", "w")) == NULL) // "w"\"r"
{
printf("Can't open this file!\n");
exit(1);
}
while((ch = fgetc(stdin)) != '\n') //从屏幕输入一个字符,回车结束
//while((ch = fgetc(fp)) != EOF) //判断文件是否到尾,-1, 必须是文本文件
{
fputc(ch, fp1); //把 ch 的值存入 fp 指向的文件 .txt
//fputc(ch, stdout); //输出 ch 的值到屏幕
}
fclose(fp);
}
void main()
{
FILE *fp;
char str[64];
if((fp = fopen("E:\\Visual Studio 2019\\hjt2.txt", "a")) == NULL) // "w"\"r"
{
printf("Can't open this file!\n");
exit(1);
}
while( fgets(str, 64, stdin) != NULL) //从屏幕输入字符串,判断字符串长度是否为0
{
fputs(str, fp); //把 str 的值存入 fp 指向的文件 .txt
fputs(str, stdout); //把 str 的值在屏幕显示
exit(0);
}
fclose(fp);
}
void main()
{
FILE *fp;
int x, y;
fp = fopen("E:\\Visual Studio 2019\\hjt3.txt", "w");
fscanf(stdin, "%d,%d", &x, &y);
fprintf(stdout, "x=%d,y=%d\n", x, y);
fprintf(fp, "%d,%d", x, y);
fclose(fp);
}
struct student
{
char num[8];
char name[10];
};
typedef struct student st;
void main()
{
st s[3] = { "1001","wang","1002","li","1003","liu" };
//st s[3];
FILE *fp;
fp = fopen("E:\\Visual Studio 2019\\hjt4.txt", "wb"); // "wb"/"rb"
int i;
for(i = 0; i < 3; i++)
fwrite(&s[i],sizeof(st),1,fp); // fwrite / fread
/*
for(i=0;i<3;i++)
{
puts(s[i].num);
puts(s[i].name);
}
*/
fclose(fp);
}