C语言的基本概念
编写一个简单的C程序,后缀名保存为c(本次文件名为a.c)
gcc:对c程序进行编译和连接;gcc a.c
./a.out:运行程序,输出程序的结果;其中a是c程序的文件名
说明:其实并不是简单的在a.c文本中输入几行代码就能运行的,其内部实现步骤如下:
1:gcc -E a.c 在c程序中有个#include的头文件,其中#开始的命令都叫做预处理命令,这里是把文件通过预处理器进行处理
2:gcc -c a.c 对文件进行编译成机器认识的二进制的格式的目标代码,然后就出现一个a.o文件
3:gcc a.o 把目标代码和其他的附加代码整合在一起,这样就有一个可以执行的程序a.out
4:执行a.out就能够得到程序的结果
注意:上面的例子中只使用了gcc a.c 对c程序进行编译和连接;然后./a.out来输出,并没有使用 1,2,3步骤;事实上gcc a.c 这一表达式已经完成编译和连接两个步骤,也就是说执行完gcc a.c之后,就已经生成out文件了,下面来了解gcc的命令参数就知道了;
gcc命令的参数讲解
-c:只编译,不连接成为可执行的文件,编译器只是由输入的.c等源代码文件生成.o为后缀的目标文件,通常用于编译不包含主程序的子程序文件
-o output_filename:确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的可执行文件a.out,所以上边例子的gcc a.c执行后就直接生成了out后缀文件,所以就省略了1-2-3步骤(gcc -o b.o a.c //这里是把a.c文件生成b.o文件,如果不为b文件指定o为后缀,那就只生成b文件)
-g:产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对源代码进行调试,我们就必须加入这个选项,这个参数一样可以生成out的后缀文件
-O:对程序进行优化编译、连接,采用这个选项,整个源代码会在编译、连接过程中进行优化处理,这样产生的可执行文件的执行效率可以提高,但是,编译、连接的速度就相应地要慢一些;这个参数一样可以生成out的后缀文件
-O2:比-O更好的优化编译、连接,当然整个编译、连接过程会更慢;这个参数一样可以生成out的后缀文件
-std:选择编译程序所使用的c语言的标准,可以使用-std=c89 或者 -std=c99;这个参数一样可以生成out的后缀文件
注释
/**/:多行注释,一般用来对描述参数,程序信息
//:单行注释,一般用来描述语法的讲解,每行语法对应一行单行注释
例如:
/*
文件名:×××
作者:×××
年份:×××
版本信息:×××
*/
#include<stdio.h> //头文件
___________________________________________________________________________________________________________________________________________________________
基本类型
char:字符类型
char c1 = "a"; //输出a
char c2 = "97"; //97是ASCII转码 代表a
char c3 = "3"; //输出3
char c4 = "/" //printf("%d",c4); //输出这个/号的ASCII转码
关键字sizeof:在printf中用来测量变量和类型在内存中所占的字节数
sizeof(c); //变量
sizeof(char); //类型
sizeof(表达式); //他只关心括号里的类型占的字节大小,不会对表达式进行运算:
int a
short o=9;
a=sizeof(o=10); //只输出o类型占内存的大小,不会输出o=10;所以a等于o占内存字节的大小,而不是10;
输出的是字节数值
无符号整型:就是没有负号的整型数,其永远只有正数
___________________________________________________________________________________________________________________________________________________________
二进制
负十进制转换为二进制
按位取反--加1
负二进制转换为十进制
减1--按位取反
___________________________________________________________________________________________________________________________________________________________
判断运算符
!= //表示不等于
逻辑运算符
&&:与
||:或
!:非
短路特性:
int i=0,j=0;
if(++i||++j); //当运算++i得出结果时,就不会再去运算++j,所以这里++j无效,j还是等于0
if(--i&&++j); //当运算--i得出结果时,就不会再去运算++j,所以这里++j无效,j还是等于0
printf("%d %d",i,j); //经过两次运算,i最后还是0,j还是0
___________________________________________________________________________________________________________________________________________________________
位运算
~:按位取反;如果是整数,运算后就是负数,同样负数运算后得到的是正数;~3=-4; ~4=-5 ;~(n+1)
&:按位与;两个二进制数进行与运算时,如果两个数都为真,其结果为真,如果两位数中有一位为假,其结果为假
|: 按位或:两个二进制数进行与运算时,只要其中一个数为正,其对应的位就为真
^:按位异或;一真一假为真,另外如果两个都是真或都是假,其结果都为假
<<:左移
>>:右移
左移一位 相当于乘以2
右移一位 相当于除以2
逗号运算符
在C语言中,多个表达式可以用逗号分开,其中用逗号分开的表达式的值分别结算,但整个表达式的值是最后一个表达式的值
假设b=2,c=7,d=5,
a1=(++b,c--,d+3); //有三个表达式,用逗号分开,所以最终的值应该是最后一个表达式的值,也就是d+3,为8,所以a1=8
a2=++b,c--,d+3; //这时的三个表达式为a2=++b、c--、d+3,(这是因为赋值运算符比逗号运算符优先级高)所以最终表达式的值虽然也为8,但a2=3。
注意:上面两个算法没有连续关系,所以第一次的++b的值不能作为第二次的++b值继续运算,因此第二次的++b结果是3
还有一种情况:
x=3,y=5;
a=(x+3,y++,x++); //x+3的结果没有指定赋值,会被丢掉,这里的a=3 ;x=4 ;y=5
a=(x++,x+3,x+7); //x++的结果保存下来,x+3的结果丢掉,a=11; x=4
___________________________________________________________________________________________________________________________________________________________
缓冲区
scanf:
#include<stdio.h>
main()
{
int data1,data2;
printf("请输入两个数字\n");
scanf("%d",&data1);
scanf("%d",&data2);
printf("%d %d",data1,data2);
}
说明:如果输入1 3 得到的结果是正确的,但是如果输入的1 r 得到的是1和0,如果输入的是r 1 那么两个输出都是0
因为,只有当第一个数据读走时,才会清除这个数据,当第一个是0时,0就不会被清除,导致第二个还是0
这里的0是指错误码。也是变量的初始值
解决方法:
scanf("%*[^\n]"); //*忽略读到的内容,[^\n]任何非\n的字符
将\n之前的所有字符读走到缓冲区,这样你输入的路七八糟的字符都会被读入到缓冲区
scanf("%*c"); //从缓冲区读取一个字符,这样就把乱码读走了,不会影响第二个数据的正常输入
实例:
#include<stdio.h>
main()
{
int data1=0,data2=0;
printf("请输入两个数字\n");
if(scanf("%d",&data1)==0) //如果=0.就执行上面的解决方法,就能把字符读走了
{
scanf("%*[^\n]"); //把\n之前的所有字符读走,包含\n,之后就不能通过\n来完成输入,每输入一个数字都要按回车键了
scanf("%*c"); //读取所有字符忽略掉
}
if(scanf("%d",&data2)==0)
{
scanf("%*[^\n]");
scanf("%*c");
}
printf("%d %d",data1,data2);
}
人工刷新输出:
fflush(stdin)刷新标准输入缓冲区,把输入缓冲区里的东西丢弃[非标准]
fflush(stdout)刷新标准输出缓冲区,把输出缓冲区里的东西打印到标准输出设备上
printf("......");后面加fflush(stdout);可提高打印效率
___________________________________________________________________________________________________________________________________________________________
数组
数组初始化
int a[5]={0}; //这样就全部都初始化了
int a[30]={[10]=4, [9]=8, [23]=67}; //这样就对指定的元素初始化,并且没有初始化的都默认为0,这样也不存在顺序问题
sizeof:测试数组的大小
sizeof(数组字节)=sizeof(数组元素字节)*元素的个数 如:int a[10]; sizeof(a)=sizeof(a[0])*10
printf("%d",sizeof (a[0])) //求数组元素的字节
printf("%d",sizeof (a)) //求数组的字节
如:int a[10] //int占4个字节,a就是40字节,在除以每个元素的字节a[0]=4,就是数组的长度
二维数组
int a[3][4]={{0}}; //对所有的元素进行初始化为0
___________________________________________________________________________________________________________________________________________________________
函数
#include<stdio.h>
#include<stdbool.h>
bool prime(int data)
{
int i=2;
for(;i<data;i++)
{
if(data%i==0)
return false; //假
}
return true; //真
}
main()
{
int d=0;
printf("输入一个数");
scanf("%d",&d);
printf("%s\n",prime(d)?"素数":"合数"); //为真选择素数,为假选择合数,输出是字符串
}
exit:
函数名: exit()
所在头文件:#include <stdlib.h>
功 能: 关闭所有文件,终止正在执行的程序。
exit(1)表示异常退出.这个1是返回给操作系统的不过在DOS好像不需要这个返回值
exit(x)(x不为0)都表示异常退出
exit(0)表示正常退出
用 法: void exit(int status);
exit()和return的区别:
按照ANSI C,在最初调用的main()中使用return和exit()的效果相同。
但要注意这里所说的是“最初调用”。如果main()在一个递归程序中,exit()仍然会终止程序;但return将控制权移交给递归的前一级,直到最初的那一级,此时return才会终止程序。return和exit()的另一个区别在于,即使在除main()之外的函数中调用exit(),它也将终止程序。
#include <stdlib.h>
#include <conio.h>
#include <stdio.h>
int main(void)
{
char status;
printf("Enter either 1 or 2\n");
status = getch(); //获取一个字符
exit(status - '0'); //status减去字符0的ASCII码
printf("%d\n",status); //字符以%d形式输出
return 0;
}
___________________________________________________________________________________________________________________________________________________________
变量的类别
局部变量:在函数体内定义的变量
生命周期:从定义这个局部变量的地方开始到函数的结束
作用范围:在定义这个局部变量的函数内
static:静态局部变量,其数值就是上一次函数调用结束之后的数值,还可以修饰函数:static int a(int a);
生命周期:整个程序
作用范围:和普通局部变量一样
全局变量:定义在整个程序中的变量称为 全局变量
生命周期:整个程序的生命周期之内
作用范围:整个程序的范围内都可以访问
全局变量在定义之后自动初始化为0
块变量:定义在 程序块 里面的变量叫做块变量
程序块:使用{ }括起来的一组语句,比如 if 语句里的或者是循环语句等使用到 { } 的地方
生命周期:定义变量的地方开始,程序块结束的地方消失
作用范围:程序块内
rand();:随机生成一个数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
main()
{
int data = 0;
srand(time(0)); //随机数发生器的初始化函数,为了防止随机数每次重复常常使用系统时间来初始化;time(0)取得当前时间(秒的总和)
data = rand()%100; //初始化随机数,取100内的数赋值给data
while(t==time(0)); //延迟 让程序等待这一秒过去
printf("%d\n",data);
}
time(0)%100 //当前秒总和
int t;
t=((time(0)%100)+5); //当前时间+5赋值给t
printf("%d\n",t); //打印t的值
while(t>(time(0)%100)); //延迟5秒。当表达式为真,执行循环,为假退出循环,刚好5秒
register:寄存器,register int a;
告诉编译器这个变量会被频繁的使用,请保存到寄存器中
不能对寄存器变量取地址&
有些系统不会把register修饰的变量放到寄存器中,由编译器决定
const:只读变量修饰符,变量的数值是不能被改变的
volatile:一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会;确保本条指令不会因编译器的优化而省略,且要求每次直接读值
XBYTE[2]=0x55;
XBYTE[2]=0x56;
XBYTE[2]=0x57;
XBYTE[2]=0x58;
对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器就不能像对待纯粹的程序那样对上述四条语句进行优化,只认为XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一的进行编译并产生相应的机器代码(四条).
定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量
3). 多线程应用中被几个任务共享的变量
___________________________________________________________________________________________________________________________________________________________
指针
int *q=NULL; //指针初始化
注意: 不要返回一个局部变量的指针
char a[10]; a="add"; //这种赋值是错的
char a[10]; a[0]='b'; //这种是对的,意思为第一个赋值一个字符b
___________________________________________________________________________________________________________________________________________________________
程序段:
就是一段程序(可以是一个子过程SUB,一个函数FUNCTION(用面向对象的观点或称为方法))
程序都是从上到下施行的,那应该什么时候用到程序段
数据段:
在采用段式内存管理的架构中,数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
bss段:
通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域
___________________________________________________________________________________________________________________________________________________________
堆和栈
堆栈二者特性不同,各有适用场合。首先,最重要的一点,对象生存期不同。栈上的空间,是自动回收的,虽然省事,但如果不想让它自动回收,就要使用堆来创建对象;在一个函数内部创建一个对象,然后把它的地址传给函数外层用,就不能在栈上创建这个对象,因为当函数一结束,此对象就被销毁了,外面访问它会出错。而堆的话由于是完全手工创建手工回收,在碰到delete之前这个对象是不会被销毁的,就可以随意传递。堆上申请空间可以很大,但是栈的空间却很有限,根据操作系统不同而不同,一般只有1~4MB的大小,如果在栈上申请过大的空间就会出错。最后,栈上申请空间的速度比堆上快得多,所以如果是函数内部临时使用的小对象,一般用栈来分配。
栈:它是一种具有后进先出性质的数据结构,也就是说后存放的先取,先存放的后取
堆:堆的存取是随意,如同我们在图书馆的书架上取书
char *p1; //全局未初始化区
main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; // 123456\0在常量区,p3在栈上。
p1 = (char *)malloc(10); 堆
p2 = (char *)malloc(20); 堆
}
栈是系统自动分配空间的,栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。
堆则是程序员根据需要自己申请的空间,例如malloc(10);开辟十个字节的空间。而堆上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。
堆内存申请:
new:申请堆内存
malloc:申请堆内存:a=(int*)malloc(100*sizeof(int));
delete []:释放堆内存空间,指向堆内存首元素的指针;如果申请的是一个堆内存变量,则delete后的[]可以省略;如果申请的是一个堆内存数组,则该[]不能省略
#include "iostream.h"
int main()
{
int size;
float sum=0;
int *heapArray;
cout <<"请输入元素个数:";
cin >>size;
heapArray=new int[size]; //申请堆内存
cout <<"请输入各元素:" <<endl;
for (int i=0;i<size;i++)
{
cin >>heapArray[i];
sum=sum+heapArray[i];
}
cout <<"这些数的平均值为" <<sum/size <<endl;
delete [] heapArray; //释放堆内存
return 0;
}
注意:这里使用到了两个输入输出关键字:cout,cin
输出输入
#include "iostream.h" //使用头文件
cout<< "result="<<"result"<<endl; //endl是输出结束,后加一个换行符
cin >>size;
___________________________________________________________________________________________________________________________________________________________
读写一个字符串
puts:输出一个字符串,可以指定一个文件指针,把字符串输出到文件中,也可以输出到显示设备上
fputs
#include<stdio.h>
main()
{
char a[]="1a3456";/*字符数组也可以定义数字,但输出的是ascll,并非数字*/
printf("%d\n",++a[0]);/*输出的是字符1的ASCII码,1的ASCII 码为49,++a[0]后等于50*/
printf("%d\n",++a[1]);/*a在ASCII码中等于97*/
printf("%c\n",++a[1]);/*这里指的字符是可以自身++的,无论是字母还是数字都可以*/
puts(a); /*定义好的字符数组,在puts中只需写上数组名就可以输出*/
puts("123456"); /*没有定义的字符或字符串就要用“”来括上*/
puts("asdf");/*puts里不允许++,如puts(++b);*/
}
gets:输入带有空格的字符串可以使用函数来进行输入,但是函数不会计算缓冲区的大小,会造成错误,溢出
fgets:这个和上面的一样,但是却多了一个可以计算缓冲区大小,保证不会溢出的功能
这个函数也可以把文本的字符串输入到程序的字符数组中来:、
fgets(str,n,fp);
str:存放字符的变量
n:指定读取字符的个数
fp:文件指针
#include<stdio.h>
main()
{
char a[10]; /*不可以把定义长度的步骤省略*/
fgets(a); /*类似于puts的使用方法*/
printf("%s\n",a); /*gets函数读入字符时,包括空格和Entre符,依次存放在数组中*/
}
___________________________________________________________________________________________________________________________________________________________
字符串操作函数(不支持二维字符数组或二维数组)
strlen:求实际长度
strupr:小写变大写
strlwr:大写变小写
#include<string.h>/*头文件指定文件包含的库函数(和include<stdio.h>指定的输出输入,函数变量道理一样)*/
printf("%d\n",strlen(str1));/*求字符串的实际长度,包括空格(也可以这样(strlen("asdf asd"))*/
printf("%s\n",strupr(str1));/*把小写换成大写*/
printf("%s\n",strlwr(str2));/* 把大写换成小写。不能小写换成小写或大写换大写,只能对换,否则输出结果不变*/
strcpy:复制
strcpy(str1,str1); //复制规则是由后边的复制给前边的字符数组
strcpy(str1,"123"); //如果是定义好的数组给前边的复制就不用加
strcat:链接)
strcat(str1,str3); //把后边的字符或字符数组链接到前边,并放在前边的字符数组中输出,输出顺序是(前11后22:1122)
strcat(str1,"llo"); //前边必须写成数组名形式,且之前必须定义....这里同样可以定义字符输入
strcmp:比较
#include<stdio.h>
#include<string.h>
main()
{
char str1[]="zzz"; //字母大于空格
char str2[]="zz zz";
printf("%d\n",strcmp(str1,str2)); //前边大,输出为正1,后边大,输出为负1
char str3[]="zz\0";
char str4[]="zz "; //空格大于\0所以str3大
printf("%d\n",strcmp(str3,str4));
char str5[]="zz\0";
char str6[]="zz\0z"; //由于两个\0相遇所以认为比较结束*/
printf("%d\n",strcmp(str5,str6));
}
说明:这个函数是比较两个字符串,比较规则是按ascll码值大小比较,顺序是从左至右逐个字符相比较,直到两组字符的\0同时出现时结束,或遇到不相同的字符时停止。并不是比较两个字符串谁的长的意思
___________________________________________________________________________________________________________________________________________________________
main()函数的形式
int main(int argc,const char* argv[]) //带参数的函数
int main(viod); //无参数形式
main() //C90标准允许这种形式,但是C99标准不允许。因此即使你当前的编译器允许,也不要这么写
坚持使用标准的意义在于:当你把程序从一个编译器移到另一个编译器时,照样能正常运行。
C编译器允许main()函数没有参数,或者有两个参数(有些实现允许更多的参数,但这只是对标准的扩展)。这两个参数,一个是int类型,一个是字符串类型。第一个参数是命令行中的字符串数。按照惯例(但不是必须的),这个int参数被称为argc(argument count)。大家或许现在才明白这个形参为什么要取这么个奇怪的名字吧,呵呵!至于英文的意思,自己查字典吧。第二个参数是一个指向字符串的指针数组。命令行中的每个字符串被存储到内存中,并且分配一个指针指向它。按照惯例,这个指针数组被称为argv(argument value)。系统使用空格把各个字符串格开。一般情况下,把程序本身的名字赋值给argv[0],接着,把最后的第一个字符串赋给argv[1],等等。
现在我们来看一个例子:
#include "stdio.h"
int main(int argc, char *argv[])
{
int count;
printf("The command line has %d arguments: /n",argc-1);
for(count=1;count<argc;count++)
printf("%d: %s/n",count,argv[count]);
return 0;
}
编译运行,在命令行输入c I love you 回车,下面是从命令行运行该程序的结果:
The command line has 3 arguments:
1:I
2:love
3:you
从本例可以看出,程序从命令行中接受到4个字符串(包括程序名),并将它们存放在字符串数组中,其对应关系:
argv[0] ------> c(程序名)
argv[1] ------> I
argv[2] ------> love
argv[3] ------> you
至于argc的值,也即是参数的个数,程序在运行时会自动统计,不必我们操心。
说明:目前我使用的编译器不支持运行代码后,能输入main的参数,只需要理解就行了
___________________________________________________________________________________________________________________________________________________________
指向数组的指针变量的运算“++,--”
*p++等价于*(P++),先得到p所指向的变量,再使指针p+1;
*(++p)等价于*++p,先使p加1,再取出*p的值。
(*p)++,表示p所指向的元素值加1,即a[0]++;
___________________________________________________________________________________________________________________________________________________________
宏定义
1:也可以对符号进行宏定义
2:带参数的宏:如下
#include<stdio.h>
#define b(x) (x)<='z'&&(x)>='a'?(x)-('a'-'A'):(x)
int main(void)
{
char c;
printf("请输入一个字符\n");
scanf("%c",&c);
printf("%c\n",b(c));
return 0;
}
说明:带参数的宏:b(x) b是标识符,对应于宏体(x)<='z'&&(x)>='a'?(x)-('a'-'A'):(x);括号里的x是参数,可以有多个参数,意思是将来把宏体的x的参数替换
多个参数的宏
#include<stdio.h>
#define b(x,V) (V)<='z'&&(V)>='a'?(V)-('a'-'A'):(V) //这里出现两个宏参数
int main(void)
{
char c;
printf("请输入一个字符\n");
scanf("%c",&c);
printf("%c\n",b(,c)); //要把宏参数的第二位V替换,所以第一位可以留空不填,但是第二位要填上实参
return 0;
}
___________________________________________________________________________________________________________________________________________________________
宏运算符
在C语言中相邻的字符串字面量会被合并:
printf("i/j"" = %d/n", i/j);
printf("i/j = %d/n", i/j);
#:运算符将一个宏的参数转换为字符串字面量:
#define PRINT_INT(x) printf(#x " = %d/n", x); //x之前的#运算符通知预处理器根据PRINT_INT的参数创建一个字符串字面量,也就是说参数x代表字符,后面的x则是普通的宏参数
PRINT_INT(hi) //调用宏hi就是参数
printf("hi" " = %d/n", x); //在宏的参数中x代表字符串,现在把字符串hi传到宏中就成了这样,这是合法的,相同于 printf("i/j = %d/n", i/j); //后面的i/j是一个公式
##:运算符可以将两标识符“粘”在一起,成为一个标识符,被称为粘合符。如果其中一个操作数是宏参数,“粘合”会在当前形式参数被相应的实际参数替换后发生。
#define STRCPY2(a, b) strcpy(a##_p, b##_p)
STRCPY2(h, s); //变成以下的
strcpy(h_p, s_p) //结果
#define MAX(type) type max_type##()
MAX(int); //变成int max_int(),第一个正常被替换,第二个被粘合在一起,正常替换的时候不能 有其他参数,所以一旦有其他参数,就能合并,这时要使用##
终止宏定义的作用域
#undef IP //终止PI作用域
----------------------------------------------
应用例子:
#include <stdio.h>
#include <string.h>
#define STRCPY1(a, b) strcpy(a##_p, #b)
#define STRCPY2(a, b) strcpy(a##_p, b##_p)
int main(void)
{
char var1_p[30];
char var2_p[30];
STRCPY1(var1, ok); //第一个宏参数var1传递给a,变成var1_p;ok传给b,变成ok;var1_p[30]=ok;
STRCPY2(var2, var1); //把var1_p赋值给var2_p
printf("var1_p= %s\n", var1_p);
printf("var2_p= %s\n", var2_p); //结构两个都是ok
return 0;
}
___________________________________________________________________________________________________________________________________________________________
预定义的宏
#include <stdio.h>
#include <stdlib.h>
void why_me();
int main()
{
printf( "%s\n", __FILE__ ); //当前正在编译的源程序的名称
printf( "%s\n", __DATE__ ); //编译程序的日期
printf( "%s\n", __TIME__ ); //编译程序的时间
printf( "%d\n", __LINE__ ); //当前正在编译的程序的行号
printf( "%s\n", __STDC__?"Y":"N" ); //判断编译器是否符合C标准
return 0;
}
char *a=__FILE__; //也可以
更多请访问网络:
___________________________________________________________________________________________________________________________________________________________
条件编译
说明:条件编译就是根据预处理器的执行结果 来包含或者排除某一段程序
#ifdef DEBUG:如果宏定义中定义了DEBUG,就对这段程序编译。如果没有宏定义,或者宏定义了别的字符,这段程序将不被执行。
#endif:由于程序中出现了#ifdef, 所以必须有endif作为程序结束。
#ifndef DEBUG:如果宏未定义DEBUG,则执行这段程序,
#endif:由于程序中出现了#ifndef, 所以必须有#endif作为程序结束。
#if M:如果宏定义的M非0则为真,-1也为真,为真时则执行这段程序
#endif:到这个位置结束,#endif下面可以有输出也可以没有,但endif一定要写,否则无结束条件则会出错。
#else:可以用在#ifndef或#ifdef中,如果#ifndef或#ifdef条件不成立则执行#else
___________________________________________________________________________________________________________________________________________________________
多文件程序的编写
常见的程序都是由多个源文件和多个头文件组成
程序分为多个源文件好处:
相关的变量和函数放在一个源文件中,程序的结构更加清晰
可以对每一个源文件分别进行编译,如果修改一个源文件只需要编译这个源文件,节约了时间
函数放在多个文件中有利于代码的复用
多个源文件编译:首先对每一个源文件只编译不连接 gcc -c 生成 .o 文件,如果要连接:gcc *.o;
也可以使多个源文件编程一个out执行文件:a.c b.c ; gcc a.c b.c ; 生成 a.out ,只需要运行a.out就能使两个源文件一起使用 //a包含了头文件和函数,b只包含函数,
___________________________________________________________________________________________________________________________________________________________
共享全局变量声明:
extern:原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
//A.cpp
extern int i;
int main()
{
i = 100; //试图使用B中定义的全局变量
}
//B.cpp
int i;
顺利通过编译,链接。
___________________________________________________________________________________________________________________________________________________________
自定义头文件
可以共享全局变量,共享宏,变量,函数,等:但是只声明不定义;宏 #define U 123 这类型不属于定义,应该是声明
先创建一个头文件 a.h
#ifndef TEE //防止头文件再次被本头文件引用,或者是其他源文件中如果定义了宏TEE,也不可以再次引用这个头文件,否则错误
#define TEE
#include<stdio.h>
#define U 123
#endif //如果应用已经包含,就不能再引用上面,要引用这里的头文件
#include<stdio.h>
#define B 123
int a; //不能省略 ; 号
int b()
{
//说明这个函数的作用
};
把头文件和源文件放在当前(同一个目录)目录中,源文件在引用头文件时只需要引用#include "a.h";
头文件可以声明变量:int a; 但是不能定义变量:int a=5; 错误的
也可以声明函数等,但都只能声明,不能对其定义算法,如果要实现算法,必须要一个mian函数入口,并且关联头文件,建议实现算法单独放在另一个 .c 文件
___________________________________________________________________________________________________________________________________________________________
文件嵌套包含
#include<stdio.h>
#include"22_2f3.cpp"
#include"22_2F2.cpp"
main()
{int a=2,m;
m=b(a);
printf("\nm=%d",m);
}
22_2F2://由于文件类型不是头文件,所以这个文件的尾叠是.cpp
int b(int i)
{
int j,k;
j=i+4;
k=c(j);
k=2*k;
printf("\nj=%d,k=%d",j,k);
return k;
}
22_2f3://由于文件类型不是头文件,所以这个文件的尾叠是.cpp
int c(int x)
{int y;
y=x*x;
printf("\ny=%d",y);
return y;
}
第一个程序包含了2,但是2中包含了3.所以要先把3包含进来再把2包含,顺序不能错,因为2没有3,2就没有结果,2没有结果,1就没有结果,整个过程就会出错。所以先是#include"22_2f3.cpp"再到#include"22_2F2.CPP"。
注意:三个文件都是在当前目录才行,否则需要指明路径,路径的格式到网上查去
___________________________________________________________________________________________________________________________________________________________
makefile 条件编译 组合编译
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令
make是一个命令工具,是一个解释makefile中指令的命令工具
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
关于程序的编译和链接
——————————
编译:编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件:在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File
链接:主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。
总结:源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File
Makefile的规则
——————————
target ... : prerequisites ...
command
target:一个目标文件,可以是Object File,也可以是执行文件
prerequisites:要生成target所需要的目标文件
command:make需要执行的命令
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行
示例:如果一个工程有3个头文件,和8个C文件
edit : main.o kbd.o command.o display.o / //要生成一个edit文件,依赖文件都是 .o
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o / //执行指令edit之后编译这些 .o 文件生成一个edit文件
insert.o search.o files.o utils.o //可以看到这些执行执行的命令和依赖文件都是 .o也就是说要下面的文件生成相应的 .o文件,段指令才有效
main.o : main.c defs.h //要生成一个main.o,依赖文件是 main.c defs.h
cc -c main.c //执行的指令是编译main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit *.o
说明:反斜杠(/)是换行符的意思
在linx的shell中,我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。
如果要生成目录文件:
make main.o
make kbd.o
make files.o
make clean //没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,需要显式执行make clean,以此来清除所有的目标文件,以便重编译
注意:编译的使用依赖文件需要有头文件 .h ,但是编译时只需要源文件 .c
在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。
在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头然后换行。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。
这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。
于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了
也就是说,只要执行命令“make”就可以生成执行文件edit,就会自动执行后面所定义的命令来生成edit这个文件,而命令使用到的 .o文件如果没有,就会去自动执行下面的命令生成所有有关系的依赖文件,因此当生成一个edit文件后,便会跟着生成下面的main.o kbd.o files.o...文件
而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接
___________________________________________________________________________________________________________________________________________________________
静态全局变量
static:它是在内存的静态存储区中占有永久的存储单元
当使用 static 声明符来说明全局变量时,称这个全局变量为静态全局变量,只能被本编译单位使用,不能被其他编译单位使用,即使其他文件使用extern也不行。
在定义全局变量时,前面的存储类别定义为static,这就是静态全局变量,即使在函数调用结束后,也不消失且保留原值(上一次的值),下次调用时,值不变(为上次的最后值)
静态全局函数 //这个函数只能在本文件中使用,和静态全局变量类型性质
___________________________________________________________________________________________________________________________________________________________
typedef:自定义类型
在计算机编程语言中用来为复杂的声明定义简单的别名,与宏定义有些差异。它本身是一种存储类的关键字,与auto、extern、mutable、static、register等关键字不能出现在同一个表达式中。
它有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法 。使用typedef可编写出更加美观和可读的代码。所谓美观,意指typedef能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性以及未来的可维护性。
typedef char Line[81]; //为char起一个别名为line,长度是81
此时Line类型即代表了具有81个元素的字符数组,使用方法如下:
Line text, secondline;
以前学C++的时候,经常遇到这种情况:
typedef struct
{
short level;
unsigned flags:
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned ar *curp;
unsigned istemp;
short token;
}FILE;
然后就能够使用 FILE 定义一个文件类型 FILE file1;
其实就是给类型起一个别名,然后用这个别名去定义一个变量:
typedef int s_int; /*把int替换成s_int*/
s_int i,t;
typedef unsigned int u_int:
u_int k,m;
typedef int* number; /*定义指针类型*/
nuber n;
常量指针和指针常量
typedef char* const pstr //指针常量,指针指向一个常量地址,用来存放常量的
typedef const char* pstr; //常量指针,指针指向一个字符地址,用来存放字符地址,并且这样只能存放一个常量地址
注意指向常量的指针所指向的内容不能改变,但是可以改变其所指向的地址。
___________________________________________________________________________________________________________________________________________________________
结构体
struct:结构体声明修饰符
结构体是不同数据类型的集合,每个成员都有自己的名字,可以通过名字来访问成员
结构体变量的定义
struct stu /*结构体名称与int特性一样*/
{
int num; /*成员*/
char *name;
char sex;
float score;
} ;
struct stu boy1,boy2; /*变量名*/
------------------------------------------------
struct stu
{
int num;
char *name;
char sex;
float score;
} boy1,boy2;/*直接定义结构体变量名*/
struct /*这里没有定义结构体名称,这样就意味着,除了boy1,boy2两个变量名称可以使用之外,就不可以再定义了,因为定义是要使用结构体名称的*/
{
int num;
char *name;
char sex;
float score;
} boy1,boy2;
------------------------------------------------
结构体嵌套
struct date
{
int month; /*月*/
int day; /*日*/
int year; /*年*/
};/*这里并没有定义结构体变量名*/
struct book
{
long num; /*书的编号*/
char name[20]; /*书名*/
struct date time; /*详细见下*/
char author; /*作者*/
}book1,book2; /*结构体名称*/
----------------------------
结构体初始化
{
struct date
{
int month;
int day;
int year;
};
struct book
{
long num;
char name[20];
struct date time;
char author[10];
}book2,book1={50,"nihao",1,1,1,"wsii"}; //初始化
book2=book1; //相同的结构体可以相互赋值
strcpy(book1.name,"nihao"); //复制
printf("%d %d\n",book1.time.day, book1.num);
}
由于struct date time中的time是struct date的结构体变量,所以在调用book结构体变量time时,只需book1.time.day就行了
----------------------------
为结构体申请一个堆
pb=(TYPE*) malloc(LEN); TYPE *student;
----------------------------
为结构体起别名:
typedef struct
{
}别名;
typedef struct a
{
}别名;
注意:这里两个意思都一样,上边的只能用别名来定义变量,下边的可以用别名也可以用a来定义变量
___________________________________________________________________________________________________________________________________________________________
结构体作为函数的参数与返回值
结构是按值传递的。也就是说整个结构的内容都复制给了形参,即使某些成员数据是一个数组
#include "iostream.h"
struct student
{
int idNumber;
char name[15];
int age;
char department[20];
float gpa;
};
void display(student arg);//结构作为参数
int main()
{
student s1={428004, "Tomato",20, "ComputerScience",84.5};//声明s1,并对s1初始化
cout <<"s1.name的地址" <<&s1.name <<endl;
display(s1);
cout <<"形参被修改后……" <<endl;
display(s1);
return 0;
}
void display(student arg)
{
cout <<"学号:" <<arg.idNumber <<"姓名:" <<arg.name <<"年龄:" <<arg.age <<endl <<"院系:" <<arg.department <<"成绩:" <<arg.gpa <<endl;
cout <<"arg.name的地址" <<&arg.name <<endl;
for (int i=0;i<6;i++)//企图修改参数的成员数据
{
arg.name[i]='A';
}
arg.age++;
arg.gpa=99.9f;
}
通过上面这个程序,我们发现在函数中修改形参的值对实参是没有影响的。并且通过输出变量s1和参数arg的成员数据name所在地址,我们可以知道两者是不相同的,即整个name数组也复制给了参数arg。
一般情况下,函数只能返回一个变量。如果要尝试返回多个变量,那么就要通过在参数中使用引用,再把实参作为返回值。然而,这种方法会导致一大堆参数,程序的可读性也较差。
当结构出现以后,我们可以把所有需要返回的变量整合到一个结构中来,问题就解决了:
#include "iostream.h"
struct student
{
int idNumber;
char name[15];
int age;
char department[20];
float gpa;
};
student initial(); //初始化并返回一个结构,是一个构造函数,这里调用了构造函数的过程
void display(student arg); //声明一个函数
int main()
{
display(initial()); //输出返回的结构
return 0;
}
void display(student arg)
{
cout <<"学号:" <<arg.idNumber <<"姓名:" <<arg.name <<"年龄:" <<arg.age <<endl <<"院系:" <<arg.department <<"成绩:" <<arg.gpa <<endl;
}
student initial() //构造函数的实现方法,并且把结构返回给结构体
{
student s1={428004, "Tomato",20, "ComputerScience",84.5};//初始化结构变量
return s1;//返回结构
}
---------------------------------------------
结构体指针作为函数参数
#include "iostream.h"
#include<stdio.h>
struct student //定义一个结构体
{
int idNumber;
char name[15];
int age;
char department[20];
float gpa;
};
void display(student *arg);//结构指针作为参数
void di(student a); //结构体作为参数
int main()
{
student s1={428004, "Tomato",20, "ComputerScience",84.5}; //声明s1,并对s1初始化
cout <<"s1.name的地址" <<&s1.name <<endl;
cout <<"学号:" <<s1.idNumber <<"姓名:" <<s1.name <<"年龄:" <<s1.age <<endl <<"院系:" <<s1.department <<"成绩:" <<s1.gpa <<endl;
student *s2=&s1; //定义一个结构体指针并赋值
display(s2); //结构体指针作为函数参数,这里的s2是一个指针,就不需要再使用&符,如果不是指针,就要使用&
cout <<"形参被修改后……" <<endl;
di(s1);
return 0;
}
void display(student *arg)
{
for (int i=0;i<6;i++)//企图修改参数的成员数据
{
arg->name[i]='A'; //结构体指针调用成员的时候只需要使用 -> 就可以调用;或者是(*arg).name[i] 也可以
}
arg->age++;
arg->gpa=99.9f;
}
void di(student arg)
{
cout <<"学号:" <<arg.idNumber <<"姓名:" <<arg.name <<"年龄:" <<arg.age <<endl <<"院系:" <<arg.department <<"成绩:" <<arg.gpa <<endl;
cout <<"arg.name的地址" <<&arg.name <<endl;
}
___________________________________________________________________________________________________________________________________________________________
结构体的对齐与互补
char 1字节
short 2字节
int 4字节
double 4字节
如果一个结构体里面有以下成员
char---- 每个是4
int-----
char----
整个结构体的长度必须保持为内部最长成员的整数倍,可以使用sizeof来查看是不是这样
#include<stdio.h>
struct student
{
char v;
int a;
char g;
}o;
int main()
{
printf("%d\n",sizeof(o));
}
如果是以下排列:
int---------
char-----
char-----
看起来是这样的,长度是8,每个char是2
___________________________________________________________________________________________________________________________________________________________
结构体位段
位段可以指定每一个成员的大小,节约内存
大小是指多少个二进制位 ;1字节占8二进制位;一个二进制位占0.125字节
但是当结构体的长度为0.75字节时,使用sizeof来查看是不能输出0.75,得到的结果是1
如果是double 定义的类型小数,就可以输出
double h=0.53;
printf("%0.2f\n",h);
输出结果是0.530000
___________________________________________________________________________________________________________________________________________________________
union:联合体/共用体
共用体是由用户定义的数据类型。几种不同类型的变量存放到同一段内存单元中,就构成一个共用体。共用体的几个成员共占用同一个内存空间。一次给几个成员赋值时,前面的成员值依次被后面的成员值覆盖。共用体变量中起作用的成员是最后一次存放的成员。
共用体的定义和结构体形式一样
union data
{
int i;
float f;
char ch;
};
data data1,data2;
union data
{
...
}data1,data2;
union
{
...
}data1,data2; /*不能再定义另外的共用体类型变量*/
---------------------
共用体对齐
char[9] int double; 字节数是9 4 4 //用运算符sizeof测试其大小为16.
这是因为这里存在字节对齐的问题,9既不能被4整除,也不能被8整除。因此补充字节到16,这样就符合所有成员的自身对齐了。
共用体变量,指针的引用和结构体引用形式一样
共用体的引用与结构体的引用形式相同;
data1.i;
data1.f;
data1.ch;
可以在定义共用体变量时对变量中的第一个成员赋值,且赋值要放在{}内。
union data
{
int i;
float f;
char ch;
}data1,data2={2008};
一个共用体变量不是同时存放多个成员的值,而是只能存放其中的一个值,也就是最后赋给他的值。例如:data1.i=200;data1.f=2.66;data1.ch='china';所以以下的输出是错误的:printf("%d,%f,%c",data1.i,data1.f,data1.ch);因为此时data1中只有最后被赋给的ch的值'china'。
共用体指针
union data *pt,x;
pt=&x;
pt->i=200;
pt->f=2.66;
pt->='china';
两个同类型的共用体变量之间赋值是可以的:data1=data2;这时两者中的内容完全相同。
不能把共用体变量作为函数参数,也不能使函数返回共用体变量,但是可以使用指向共用体变量的指针(与结构体变量这种用法相仿)。共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。
#include<stdio.h>
#include<stdlib.h>
struct /*定义结构体*/
{
char name[20];
int age;
char job;
union /*结构体包含共用体*/
{
int clas;
char office[10];
}department;
}guy[3];
main()
{
int i,j;
for(i=0;i<3;i++) /*为结构体和共用体赋值*/
{
printf("Input the name,age,job and department of everyone; \n");
scanf("%s %d %c",guy[i].name,&guy[i].age,&guy[i].job); /*无论是结构体还是共用体,凡是数组的成员都不要用&运算符*/
if(guy[i].job=='s')
{
scanf("%d",&guy[i].department.clas);
}
else if(guy[i].job=='t')
{
scanf("%s",guy[i].department.office);
}
}
printf("name\tage job class/office\n");
for(i=0;i<3;i++) /*输出数据*/
{
if(guy[i].job=='s') /*如果是学生,输出的是部门是D否则是S*/
{
printf("%s\t%3d%3c\t%d\n",guy[i].name,guy[i].age,guy[i].job,guy[i].department.clas);
}
else
{
printf("%s\t%3d%3c\t%s\n",guy[i].name,guy[i].age,guy[i].job,guy[i].department.office);
}
}
}
共用体变量也可以定义成数组或指针。定义为指针时,可以用 ->符号,此时就可以用“共用体名->成员名”来访问了。
___________________________________________________________________________________________________________________________________________________________
enum:枚举类型
枚举类型属于常量
枚举类型就是将变量或对象可能存在的情况,也可以说是可能的值一一例举出来。
枚举的定义
enum 枚举名
{
标识符,
标识符,
标识符,
标识符
}枚举变量;
每个标识符称为枚举常量,他们构成这个枚举类型的所有可能的取值集合,该枚举类型的变量的取值只能限于该集合,若省略“=整形常量”时则从第一个标识符开始,顺次赋给标识符0,1,2...。但当枚举中的某个成员赋值后,其后的成员按依次加1的规则确定其值,如下:
enum numbers{a,b,c,d,e,f}num1; //因为枚举变量num1中的枚举常量都没有被赋值,则从a开始,常量的值分别为1,2,3,4,5
enum numbers(类型名){a=2,b,c=8,d,e,f}num1; //从a开始,常量的值分别为2,3,8,9,10,11
枚举元素是常量,一旦定义就不可以在程序中通过语句更改。可将枚举常量赋值给枚举变量,但不能给枚举元素常量赋值。而且枚举常量也不是字符串,而是整形量。
直接给枚举变量赋值,而非常量赋值给枚举常量,便要强制转换。
num1=(enum numbers)3; 相当于num1=3; //不能够再外部对枚举成员赋值
进行比较:if(five!=4) if(one<three);
enum numbers{one=1,two,three,four,five}; //numbers相当于一个类型,{}里成员是numbers的特性组数值
enum numbers num1,num2,num3; //num1相当于numbers的变量,不能把numbers赋值给变量num1,可以把枚举成员赋值给num1,前提是num1是numbers枚举类型的变量
实际上numbers{one=1,two,three,four,five}成员就是用来为变量num1,num2...赋值的
枚举类型的引用
输出1-5的乘法列表
#include<stdio.h>
main()
{
enum numbers{one=1,two,three,four,five}; //定义枚举常量
enum numbers num1,num2,num3; //定义枚举变量
int i,j,k;
num1=one; //把常量赋给变量
for(i=num1;i<=five;i++)
{
for(num2=one,j=num2;j<=i;j++)
{
k=i*j;
num3=(enum numbers)k; /*强制转换*/
printf("%d*%d=%d\t",i,j,num3);
}
printf("\n");
}
}
___________________________________________________________________________________________________________________________________________________________
动态内存分配
动态内存是由用户手动分配的,不同于静态内存,是由系统自动分配的;动态内存=堆;静态内存=栈
void *calloc(size_t nmemb,size_t size); //分配内存块,对内存进行清零
void *realloc(void *ptr,size_t size); //调整先前已经分配的内存块大小
void *malloc(size_t size); //分配内存块,不会对分配的内存进行清零
void *free(void *ptr); //释放动态分配的内存
void* 通用指针(无类型指针),本质就是地址、
分配成功:返回分配的内存的指针
分配失败:返回NULL
___________________________________________________________________________________________________________________________________________________________
const 与指针
const int *p; //指针指向的数据是只读的,指针本身可以改变
int *const p; //指针指向的数据是可以修改的,指针本身是只读的
const int *const p; //指针指向的数据是只读的,指针本身是只读的
___________________________________________________________________________________________________________________________________________________________
二级指针
int **q=&q; //q指针,指向一个指针
任何值都有地址 ,一级指针的值虽然是地址,但这个地址做为一个值亦需要空间来存放,是空间就具有地址 ,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址
指针交互可以用二级指针实现:
___________________________________________________________________________________________________________________________________________________________
指针数组
int *q[5];
本质上就是数组,数组中的每一个元素都是指针
数组指针
int (*q)[5]; // &q[5];数字指针在输出时,需要取址符
本质上就是指针,这个指针指向一个数组
函数指针
int *q(); //返回一个指针
int (*f)() //指向一个函数,函数返回值为int
本质上就是指向函数的指针
通用指针:void *
#include<stdio.h>
main()
{
void *z,*c;
int a=6;
char b='d';
z=&a;
c=&b;
printf("%d %c\n",*(int*)z,*(char*)c); //输入前需要强制转换
}
注意以下赋值的差异
int a[3][4]; int (*q)[4]; q=&(a[0]); q[0]::a[0][0] q[1]::a[1][0] q[2]::q[2][0] //正确
int a[3][4]; int (*q)[4]; q=&a; q[0]::a[0][0] q[1]::a[0][1] q[2]::a[0][2] //未得到预期结果
int a[4]; int (*q)[4]; q=&a; q[0]::a[0] q[1]::a[8] q[2]::a[12] //输出错误
___________________________________________________________________________________________________________________________________________________________
文件操作:
每一个打开的文件在内存里面,都有一个结构体来保存文件的信息:大小,位置,修改时间,权限等等
这个结构体是系统定义的:
typedef struct
{
short level;
unsigned flags:
char fd;
unsigned char hold;
short bsize;
unsigned char *buffer;
unsigned ar *curp;
unsigned istemp;
short token;
}FILE;
FILE类型是用来存放文件的信息;FILE f[5]; 用来存放5个文件的信息。
FILE *fp; fp是一个指向FILE类型结构体的指针变量。
文件的操作函数:
fopen:打开文件
FILE *fp; fp=fopen("c:\file.dat","r");(文件名,使用文件方式);
或者char f1[10]="c:\file.dat"; fp=fopen(f1,"r");
功能:按使用文件方式打开一个指定文件,函数返回一个指向FILE类型的指针,打开失败返回NULL
fclose:关闭文件。
fclose(文件指针);
fclose(fp); //返回值为0成功,否则为失败
功能:关闭文件指针所指向的文件,使文件指针与文件脱离关系。
文件的使用方式:
"r" (只读) 为输入打开一个文本文件,要文件已经存在
"w" (只写) 为输出打开一个文本文件,文件可以不存在,存在会覆盖
"a" (追加) 向文本文件尾增加数据,如果不存在就新建一个
"rb" (只读) 为输入打开一个二进制文件
"wb" (只写) 为输出打开一个二进制文件
"ab" (追加) 向二进制文件尾增加数据
"r+" (读写) 为读/写打开一个文本文件,文件的头部开始
"w+" (读写) 为读/写建立一个新的文本文件,覆盖
"a+" (读写) 为读/写打开一个文本文件,追加
"rb+" (读写) 为读/写打开一个二进制文件,文件的头部开始
"wb+" (读写) 为读/写建立一个新的二进制文件,覆盖
"ab+" (读写) 为读/写打开一个二进制文件,追加
成功:返回一个指向打开文件的文件指针
失败:返回NULL
----------------------------------------------------------------------------------------------
读写一个字符
fputc(ch,fp); 可以使用fputc函数在内存中输入一个字符到指定的文件。
功能:向一个打开的文件写入一个字符。ch为待输出的字符,可以是一个字符常量或一个字符变量。
fgetc:从文件中读取一个字符出来
ch=fgetc(fp); 可以使用fgetc函数在指定的文件中输出一个字符到显示器。但文件必须是以只读或读写方式打开。
如果在执行的过程中出错,则返回EOF(其值为-1);成功返回读到的字符
ch=fgetc(fp);
while=(ch!=EOP)/*判断读写的字符是否出错,或者是输入结束*/
{
putchar(fp);
ch=fgetc(fp);
}
实例:
#include<stdio.h>
main()
{
FILE *fp=NULL;
char str[10]="weather"; //创建字符串
if((fp=fopen("a.txt","w"))==NULL) //创建文件
{
printf("error\n");
return 0;
}
printf("success\n");
int i=0;
while(str[i]!='\0')
{
fputc(str[i],fp); //向文件中写入字符
i++;
}
fclose(fp); //关闭文件
return 0;
}
----------------------------------------------------------------------------------------------------------
打开文件时可以使用main函数的参数:文件地址
int main(int argc, char *argv[])
直接引用:argv[1]; //就是程序的地址
例子:
在ulix的shell中指定运行一个程序时:/a.out a.txt b.txt :a.txt和b.txt的字符就传到char *argv[],int argc的值就为2
argv[1]=a.txt; argv[2]=b.txt; 这两个字符就可以作为文件的地址使用
同样也可以使用这个方法去传递main函数的参数
读写二进制文件
fread(buffer,size,count,fp); //读取
fwrite(buffer,size,count,fp); //写入
buffer:该参数是一个 void* 无类型指针,非数组参数注意使用&符,缓冲区,fread保存了读出来的数据;fwrite保存即将写入的数据
size:读写一次的字节数;struct a b; sizeof(b);代表读写一次的字节数,如果结构体数组,可以指定一次读取多少组结构体;另外int double等类型都可以使用sizeof关键字
count:进行多少次读写
fp:文件指针
例:float a[10]; fread(a,4.2,sp);
a是一个实型数组名,一个实型变量占4个字节。从sp所指向的文本文件中读出2个占4个字节的数据,存储到a[0],a[1]中,由于a是存储对象,所以sp每给a存储一个数据时,sp和a的文件指针位置都会向下一位移动直到存储完2个数据为止
___________________________________________________________________________________________________________________________________________________________
文件的定位
文件中都有一个位置指针,指向当前读写的位置,顺序的读写一个文件的时候,当操作完当前的元素之后,位置指针自动的指向下一个元素位置。
rewind(文件指针); //使位置指针回到文件的开头
ftell(文件指针); //获取文件位置指针的当前位置;long int k; k= (fp);
fseek(文件指针,位移量,起始点); //把指针调整到指定的位置
位移量:为正数,向前移动;为负数,向后移动
起始点参数:
文件开头:0 SEEK_SET
当前位置: 1 SEEK_CUR
文件末尾: 2 SEEK_END
fseek(fp,20L,0);/*将位置指针移到离文件头20个字节处*/
fseek(fp,10L,1);/*将位置指针移到离当前位置10个字节处*/
fseek(fp,-30L,2);/*将位置指针从文件末尾处回退30个字节*/
注意:"L"是字节的意思
___________________________________________________________________________________________________________________________________________________________
变长参数
#include<stdarg.h> //变长参数所需头文件
va_list v; //定义一个可变长参数的列表
va_start(v,cnt); //将所有的参数保存到va_list中,并把参数个记录在cnt中;cnt是接收函数的第一形参
变量=va_arg(v,数据类型); //从va_list中取出第一个参数赋值给变量,变量的数据类型必须和参数的数据类型一致
va_end(v); //释放可变长参数列表
-------------------------实例
#include<stdio.h>
#include<stdarg.h>
int max(int cnt,...);
int main()
{
printf("%d\n",max(3,15,24,18));
printf("%d\n",max(5,15,51,18,23,16));
printf("%d\n",max(10,15,24,18,52,123,14,57,48,62,31));
return 0;
}
int max(int cnt,...) // '...' 代表可变数目形参列表,也就是说调用该函数的时候可以传递不定数目的实参
{
va_list v; //声明一个va_list类型的变量,用来保存参数
va_start(v,cnt); //将所有的参数保存到va_list中,并且参数的个数被cnt记录
int i;
int maxvalue=va_arg(v,int); //从va_list中取出第一个参数赋值给maxvalue
for(i=1; i<cnt; i++)
{
int data =va_arg(v,int); //从va_list中取出一个参数赋值给data,int是说明参数的数据类型
if(data>maxvalue) //进行判断
maxvalue = data;
}
va_end(v); //.释放va_list
return maxvalue; //返回一个值给max函数
}
说明:该函数作用是求最大值,所以要把最大值(比较的结果)返回给调用处
___________________________________________________________________________________________________________________________________________________________
标准库
C语言提供了一个标准的库文件,也就是头文件,每种头文件都支持不一样的功能,详细可以到网络上检索