C语言基础

基本输入输出
C语言本身并没有输入输出功能的语句,数据的输入输出都是通过函数调用来实现的。
C语言提供了标准函数库,其中就包括了基本输入输出函数,C语言把这一类的函数的定义放在了头文件stdio.h中,
要使用这些函数时只需要添加一个编译预处理命令#include<stdio.h>即可,关于头文件的作用后面还会详细介绍

字符输出函数 putchar()
一般格式:
putchar(表达式)
功能: 函数功能是在屏幕上输出一个字符 表达式可以是字符或整型表达式,表达式为将要输出的字符本身或ASC||码,
因为ASC||码只有0~255,所以当表达式大于255时,系统会自动取256的余数,当然如果表达式不是整型值时系统会自动舍弃小数部分,
有趣的是,有些ASC||码是无法显示的,比如7表示为计算机扬声器响一声

字符输入函数getchar()
一般格式:
getchar()
功能:从标准输入设备(键盘)上输入一个字符(需要回车),当用户输入一串字符时(需要回车)只返回所输入的第一个字符,如果直接回车则getchar()的值是回车,函数getchar()的值是其获得的字符;下面的语句都是正确的
c=getchar();
c=getchar()+1;
putchar(getchar());
单独使用getchar()而不做其他处理,有使程序暂停的作用,待用户按下回车时继续,

字符输入函数 getche()和getch()
一般格式:
getche()
getch()
这两个函数与getchar()大致相同,但有两点不同,一是,不需要回车,所以不会发生输入多个字符的情况,
二是,getche()输入时会回显屏幕但getch()不会

标准格式输出函数 printf()
一般格式:
printf(格式控制字符串,输出值参数列表);
关于格式控制详细内容请百度

格式输出函数 scanf()
一般格式:
scanf(格式控制字符串,变量地址列表)

变量
在c语言中,每一个变量和函数都有两个属性: 数据类型(如整型、字符型)和数据的 存储类别(静态存储和动态存储)
从变量的作用域(即从空间)角度来分,可以分为 全局变量局部变量
从变量值存在时间(即生存期)角度来分,可以分为 静态存储方式动态存储方式
静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式是指在程序运行期间根据需要进行动态的分配存储空间的方式
关于存储类别具体包含 4种,自动的( auto)、静态的( static)、寄存器的( register)、外部的( extern

auto变量
函数局部变量如果不专门声明为static存储类别,都是动态随机分配存储空间的,数据存储在动态存储区中。
函数中的形参和在函数中定义的变量(包括复合语句定义的变量),都属此类,
在函数调用时系统会给这些变量随机分配内存,一旦调用结束就会自动释放这些存储空间,因此这类局部变量称为自动变量
关键字“auto”可以省略, 不写则隐含确定为“自动存储类别”,它属于动态存储方式

static声明局部变量
有时希望函数的局部变量的值在函数调用结束后不消失而保留原值,
即其占用的存储单元不释放,在下一次调用时,该变量已有值,就是上一次调用结束时的值。
这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。
静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不释放

register变量
一般情况下,变量(包括动态存储和静态存储方式)的值是存放在内存中的。
如果有一些变量使用频繁(例如,在一个函数中执行10000次循环,每次循环都要引用局部变量),则为存取变量的值要花费不少时间。
为提高执行效率,C语言允许将局部变量的值存放在cpu的寄存器中,由于寄存器的存取速度远高于内存存取速度,因此可以提高执行效率。
这种变量叫做寄存器变量,用关键字register作声明。
只有局部自动变量和形式参数可以作为寄存器变量,其他(如全局变量)不行。
一个计算机的寄存器数目有限,而且不同的系统对register变量的处理不同,
有的系统对register变量当做自动变量处理,分配内存单元并不把它们分配到寄存器中,
有的系统只允许将int、char和指针型变量定义为寄存器变量,
局部静态变量不能定义为寄存器变量(不能写成register static int a;),
实际上当今的优化编译系统可以自动识别使用频繁的变量将它们自动放在寄存器中,而不需要程序设计者指定, register 变量做了解即可

extern声明外部变量
外部变量是在函数外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。
在此作用域内,全局变量可以为程序中各个函数所引用,编译时将外部变量分配在静态存储区。
有时候需要extern来声明外部变量,以扩展外部变量的作用域。
如果外部变量不在文件的开头定义,其作用域只限定在定义处到文件结束。
用extern声明则可以在整个文件中(包括定义该变量前的语句)使用该变量。
例如
#include<stdio.h>
void main()
{
int max(int,int);
extern A,B;/*外部变量声明*/
printf("%d\n",max(A,B));
}
int A=13,B=8;/*外部变量定义*/
int max(int x,int y)
{
int z;
z=x>y?x:y;
return(z)
}
运行结果是 13

宏定义
在C语言源程序中 允许用一个标识符来表示一个字符串,称为 ,标识符称为 宏名
在编译预处理时,对程序中所有出现的”宏名“,都用宏定义中的字符串去替换,
这个过程称为“宏替换”或“宏展开”;在C语言中宏分为有参数和无参数两种

无参宏定义
其定义的一般形式如下
#define  宏名称标识   宏字符串
如:
#define PI 3.14
#define PR printf
宏定义必须写在函数之外
宏名一般在习惯上用 全大写标识符,当然这只是习惯上
宏展开时只是做简单的替换,并不检查正确性,只有进入编译阶段时才会发现错误
宏也可以嵌套定义
值得注意的是,在源程序中用 双撇括号“”括起来的字符串中,即使出现了和宏名相同的字符序列也不进行替换
宏定义的作用域为宏定义命令起到源程序结束,如果想要终止其作用域可以使用 #undef
例如:
#define PI 3.14
main()
{
... 
}
#undef PI
f1()
{
...
}
表示PI在f1中无效

带参宏定义
宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数;对带参数的宏,在调用中不仅要展开宏,还要用实参去替换形参
带参宏定义的一般形式:
#define    宏名(形参表)  字符串
值得注意的是,宏名与形参表之间不能有空格出现

带参数的宏调用的一般形式:
宏名(实参表)

因为在带参宏定义中,形参不分配内存单元,因此不必做类型定义。而宏调用中的实参有具体值,要用他们去替换形参,因此实参必须作类型说明
这与函数中不同,函数中形参和实参是两个不同的量,只是作用域不同,在调用时进行“值传递”;在带参宏中,只是符号替换,不存在值传递的问题
在宏定义中的形参是标识符,而宏调中的实参可以是表达式
#define SQ(y)*(y)
int main()
{
int a,sq;
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
宏展开时用a+1替换y,再用(y)*(y)替换SQ,得到sq=(a+1)*(a+1)
值得注意的是,在宏定义中,形参要用括号括起来以避免出错,不光如此,形参后面的字符串也要用括号括起来,否则会出现意想不到的运算结果
宏定义也可用来调用多个语句,
#define    SSSV(s1,s2,s3,v)    s1=l*w;s2=l*h;s3=w*h;v=w*l*h;
int    mian()
{
int l=3,w=4,h=5,sa,sb,sc,vv;
SSSV(sa,sb,sc,vv);
printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",sa,sb,sc,vv);
}

文件包含
文件包含是C预处理的一个重要功能,格式一般如下:
#include    “文件名”
 文件包含的功能是把指定的文件插入到该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连成一个源文件
文件包含是很有用的,比如有些公共的符号常量或宏定义等可以单独组成一个文件,在其他文件的开头用包含该文件即可使用,这样避免重复的去写那些公共量
包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来
#include“stdio.h”
#include <math.h>
尖括号表示在包含文件目录下去查找(包含目录时由用户在设置环境时设置的),而不在源文件目录下查找
双引号则表示首先在源文件目录下查找,若未找到才到包含目录中查找
一个include命令只能指定一个被包含文件,若有多个文件要包含,则用多个include命令
文件包含允许嵌套

条件编译
条件编译即按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件
条件编译有三种形式
第一种形式:
#ifdef    标识符
        程序段一
#else
        程序段二
#endif
功能是,如果标识符已经被#define命令定义过则对程序段一进行编译,否则对程序段二进行编译
如果没有程序段二,本格式中#else可以不写
#ifdef    标识符        
          程序段一
#endif
例如:
#define NUM ok
int main()
{
...
#ifdef NUM
printf("...")
#else
printf(">>>")
#endif
}
因为程序中插入了条件编译预处理命令,因此要根据NUM是否被#define定义过来决定对哪个printf进行编译,
而在第一行中出现了#define NUM ok即NUM被#define定义过,所以执行 printf("..."),其实只要有 #define NUM存在不管后面是什么字符串
即使是 #define NUM也具有同样意义
第二种形式:
#ifndef    标识符
    程序段一
#else
    程序段二
#endif
很明显此种形式与第一种形式相反,
如果标识符已经被#define命令定义过则对程序段二进行编译,否则对程序段一进行编译
第三种形式:
#if    常量表达式
    程序段一
#else
    程序段二
#endif
它的功能是,如果常量表达式的值为真(非零),则对程序段一进行编译,否则对程序段二进行编译
例如:
#define     R     1
int main()
{
...

#if     R
printf("...");
#else
printf(">>>");
#endif
}
很明显结果是...

指针变量
在C语言中,允许用一个变量来存放“地址”,这种变量称为指针变量
在这里地址的说法并不准确,因为在C语言中,一种数据类型或数据结构往往都占用一组连续的内存单元,用地址并不能很好的描述一种数据类型或数据结构,
而指针虽然也是一个地址,但它的值却是 一个数据结构的首地址,它指向一个数据结构,所以为了表示的更清楚,应该说
在C语言中,允许用一个变量来存放指针,这种变量称为指针变量;虽然这看上去并不容易理解

指针变量的定义:
类型说明符    *变量名
例如
int     *p;
这里的int表示该指针变量所指向的变量的数据类型,*表示p是一个指针变量,下面还会讲到*,不过那是另一个含义请注意区分
至于p究竟指向哪一个整形变量,应由像p赋于的地址来决定

指针变量的赋值:
指针变量不仅要定义还要赋值,未经赋值的指针变量是一个随机值,所以这是不能被使用的,否则还造成系统混乱
指针变量只能赋予内存地址,决不能赋予其他任何数据,但是在C语言中用户并不知道变量的地址,变量的地址是由编译系统分配的,
不过还好,C语言提供了取地址运算符&来表示变量的地址
其一般形式为:
&变量名
例如&a就表示变量a的地址,&b就表示变量b的地址
(1)定义指针变量时赋初值的方法:
int     a;
int*p=&a;
或者:
int     a,*p=&a;
(2)使用赋值语句单独赋值的方法:
int     a,*p;
p=&a;
值得注意的是已经定义好的指针变量前不能再加*说明符,如* p=&a就是错误的,这个语句会被程序理解成另外一个意思,这在下面会讲到

指针运算符&和*
取地址运算符&:其功能是取变量的地址,这个已经在前面多次提到过
取内容运算符*:用来表示指针变量所指向的变量的值,在*运算符之后所跟的必须是指针变量
值得注意的是指针运算符*和前面的指针说明符*并不是一回事
如果有整形变量a和指向整形变量的指针p,并且有赋值p=&a,那么表达式*p和a是等价的,当然表达式*p+1和a+1也是等价的

指针变量的运算
赋值运算
(1)定义时初始化赋值
int     a,*p=&a;
(2)一个变量的地址赋给指针变量
int     a,*p;
p=&a;
(3)一个指针变量的值赋给另一个相同类型的指针变量
int     a,*pa=&a,*pb
pb=pa;
由于pa和pb均是指向整型变量的指针变量,所以可以相互赋值,但是不同类型的指针变量不可以相互赋值
(4) 把数组的首地址(数组名)赋予指针变量
int    a[5],*p;
p=a;                /*数组名表示数组的首地址,故可以赋值*/
当然也可以写成p=&a[0],数组第一个元素的地址也是整个数组的首地址
(5)把字符串首地址赋予指针变量
char      *p;
p=“c language”;
或采用初始化赋值char     *p=“c language”
这里并非把整个字符串装入指针变量,而是把存放该字符组的首地址装入指针变量,关于这一点后面还会详细介绍
(6)把函数入口地址赋予指向函数的指针变量
int    (*p)();         /*p为指向整形函数的指针*/
p=f;        /*f为整形函数名*/

单个指针变量的加减算术运算
对于指向数组的指针变量,可以执行加减整数的运算,设p是指向数组a的指针变量,那么
pa+n,pa-n,pa++,++pa,pa--,--pa等运算都是合法的
指针变量加减运算的意义就是 把指针当前所指向的位置(即指向的某数组元素)向前或向后移动n个(以一个数组元素为单位)位置

两个指针变量之间的 相减运算
这种运算只有对 指向同一数组的指针才有意义,所减只差就是两指针所指元素 之间相差的元素个数
实际上是两个指针值(地址)相减之差再除以该元素的字节长度,不过目前不必对此深究;当然两个指针变量之间是不能进行加法运算的

两指针之间的关系运算
指向同一数组的两指针变量进行关系运算可以表示他们所指向的数组元素之间的关系
例如:
p1==p2;表指向同一内存单元或数组
p1>p2;表p1处于高地址位置
p1<p2; 表p1处于低地址位置
指针变量还可以与零比较,p为指针变量,则p==0表p是空指针不指向任何变量;同理p!=0表不是空指针,空指针可以由对指针变量赋0值得到
int     *p=0;

指针与数组
数组时内存中的一组连续地址空间,其首地址是数组名是一个常量;指针变量既可以指向一个数组,也可以指向任何一个数组元素;
指针的值是一个地址,其值加一表示让指针指向后一个数据,这样就可以访问到该数组的所有元素

指向一维数组的指针
将一维数组元素的首地址赋值给一个指针变量,这个指针就成了指向一维数组的指针
例如
int     a[10],*p;
p=a;      /*或者写成p=&a[0]意义是一样的*/
此时指针p指向整个数组,也就是指向a[0]
当然也可以让指针指向某一个数组元素,例如:
p=&a[3];
或者写成p=a+3;
初始的时候,p指向数组的首地址,可以通过赋值运算改变指针所指,来访问不同的数组元素

指向二维数组的指针变量
这里以二维数组为例来介绍指向多维数组的指针变量
设有整型数组a[3][4]定义如下:
int     a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
C语言规定,二维数组可以看成多个一维数组的组成,例如上数组有三行可以看成是由三个一维数组组成的,他们是a[0],a[1],a[2]
可以把 a[0],a[1],a[2]看成一维数组的名字,这个一维数组有四个元素,以a[0]为例,分别是a[0][0],a[0][1],a[0][2],a[0][3]
可以这样理解,二维数组也是一个一维数组,而其中的数组元素又是一个一维数组
那么a[0]就是第零个元素(也就是二维数组第零行的首地址),第零行的首地址是&a[0]也可以写成a+0(也可以说是a[0][0]的地址),
同理 第一行的首地址是&a[1],也可以写成a+1(也可以说是a[1][0]的地址)
同理又可推出a[0]+0就是a[0][0]的地址, a[0]+1 就是a[0][1]的地址, a[0]+2 就是a[0][2]的地址
显然可以知道*(a[0]+0)和a[0][0]是等价的,即*(a[i]+j)和a[i][j]是等价的
因为a[i]和*(a+i)是等价的,所以得到,*(*(a+i)+j)就是a[i][j]

指向二维数组的指针变量说明的一般格式:
类型说明符(*指针变量名)[长度]
例如:
int     (*p)[4]
值得注意的是,这里的 长度是指分解成一维数组后的长度,即列数

不管是一维数组还是二维数组其本质都是内存中一块连续的地址空间,所以 即使定义一个普通的指针变量,也可以访问数组
当然也可以将一段存有某一类型数据的内存解释成另外一种数据类型
下面用两个例子说明这一情况
#include<stdio.h>
int main(){
    int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
    int *p,i,j;
    p=(int*)a;/*将二维数组的首地址a转成普通整型地址*/
     for(i=0;i<12;i++,p++){
          printf("%2d",*p);
    }
}
执行程序结果是1 2 3 4 5 6 7 8 9 10 11 12

#include<stdio.h>
int main(){
    char a[16]={"THIS IS A BOOK!"};
    long int      *pi=(long      int     *)a;
    char      *pc=(char*)a;
     for(putchar('\n');pi<(long     int      * )(a+16);pi++)      printf("%ld",*pi);
     for(putchar('\n');pc<(char      * )(a+16);pc++)      printf("%ld",*pc);
    getchar();
}
执行结果是1397311572 542329120 1329733697 2181967
THIS IS ABOOK!

指向字符串的指针
将字符串中的某个元素地址(通常是首地址)赋值给一个指针变量,这个指针变量就成了指向字符串的指针
指向字符串的指针实际上也是指向一维数组的指针
指向字符串的指针变量与指向字符的指针变量说明是相同的,都是字符型指针,实际上字符串指针在某一时刻指向的正是字符串中的一个字符
#include<stdio.h>
int main()
{
char c1='a',c2[8]={"abcd"};
char *p1,*p2; /*p1为字符指针,p2为字符串指针*/
p1=&c1;
p2=c2; /*注意两者赋值格式*/
printf("\n%c",*p1);
printf("\n%s",p2);/*注意字符串输出时p2前无取内容符号*******************************************************/
}

输出结果
a
abcd

指针变量做函数参数
数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的首地址,形参得到首地址后也指向同一数组
同样,指针变量的值也是地址,数组指针变量的值就是数组元素的地址,当然也可以作为函数的参数使用
下面看两个例子:
#include<stdio.h>
void swap1(int x,int y)          /*函数内部传参不会影响到实参*/
{
int t;
t=x;
x=y;
y=t;
}
void swap2(int *x,int *y)          /*指针变量做参数会直接影响到实参*/
{
int t;
t=*x;
*x=*y;
*y=t;
}
int mian()
{
int a=5,b=8;
printf("\n%d,%d",a,b);   /*输出结果是5,8*/
swap1(a,b);
printf("\n%d,%d",a,b);   /*不会影响到实参,输出结果是5,8*/
swap2(&a,&b);                /*注意指针做参数是&a取地址*/
printf("\n%d,%d",a,b);      /*指针变量做参数会直接影响到实参,输出结果是8,5*/
}
实现一个把字符串(不超过80)的内容复制到另一个字符串当中,不能使用strcpy函数
这里可以定义一个函数,使其形参为两个字符指针变量,一个指向要复制的字符串,一个指向目标字符串
#include<stdio.h>
void copystr(char *pss,char *pds)
{
while((*pds++=*pss++)!='\0'); /*\0是字符串结束标志*/
}
int mian()
{
char *pa="hello",b[81],*pb;
pb=b;
copystr(pa,pb);
printf("string a=%\nstring b=%s\n",pa,pb);
}
输出结果是string a=hello
string b=hello

使用字符串做指针变量与字符数组的区别
字符串指针变量本身是一个变量,用于存放字符串的首地址,而字符数组是由若干个数组元素组成的;
当一个字符串指针变量在未取得地址时是无法使用的,但可以通过直接赋值来让系统自动分配确定的地址;

函数指针变量
C语言中函数总是占有一段连续的内存区,而函数名就是该函数所占内存区的首地址;
可以把这个 首地址(即函数名)赋给一个指针变量,使指针变量指向该函数,然后通过指针变量就可以找到并调用这个函数,
把这种指向函数的指针变量称为“函数指针变量”
其定义的一般形式:
类型说明符     (*指针变量名)(参数说明列表);注意这里是参数说明列表不是参数列表;
调用函数的一般形式:
(*指针变量名)(实参表)
#include <stdio.h>
int max(int a,int b)
{
    if(a>b)
        return a;
    else
        return b;
}
void main()
{
    int max (int a,int b); 
    int (*pmax)(int,int);     /*参数说明列表*/
    int x,y,z;
    pmax=max;                     /*函数名赋给该函数指针变量*/
    printf("input two numbers:\n");
    scanf("%d%d",&x,&y);
    z=(*pmax)(x,y);               /*调用指针,注意指针变量两边括号不能少,*在这里只是一种符号(代表pamx是指针变量)*/
    printf("maxmum=%d",z);
}

指针型函数
C语言允许一个函数的返回值是一个指针(即地址),这种函数称为指针型函数
定义指针型函数的一般形式:
类型说明符     *函数名(形参表)
{
    /*函数体*/
}

其中函数名前加星号*,表示这是一个指针型函数,类型说明符表示返回的指针值所指向的数据类型
#include<stdio.h>
int main(){
int i;
/*如果函数在main函数上面或者返回值为整型时就不用声明,
函数声明的格式:函数类型标识符    函数名(形参表列);
或者省略参数列表直接写:函数类型标识符    函数名()*/
char *day_name(int n);
printf("input Day No:\n");
scanf("%d",&i);
printf("Day No:%2d-->%s\n",i,day_name(i));
}
char *day_name(int n){
static char *name[]={"NOT DEFINE","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
return((n<1||n>7)?name[0]:name[n]);
}

指针数组
如果一个 数组的所有元素都是指针类型变量,那么这个数组就是 指针型数组
指针数组得所有元素都 必须是具有相同储存类型和指向相同的数据类型
指针数组的一般形式:
类型说明符     *数组名[数组长度];
其中类型说明符为指针数组所指向的变量类型
例如:int     *pa[3];
表示pa是一个指针数组,有三个元素,每个元素值都是一个指针,指向整型变量;
指针数组也常用来表示一组字符串,这时指针数组的每一个元素被赋予一个字符串的首地址;
指针数组也可用作函数参数

指向指针的指针变量
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量
前面介绍到,通过指针访问变量称为间接访问,简称间访;那么指向指针的变量就是二级间访,
虽然在C语言中未对访问级数作明确限制,但间访级数过多会不容易理解,而且容易出错,所以一般很少超过二级间访

指向指针的指针变量说明的一般形式:
类型说明符     **指针变量名;
如:
int     **pp;
表示pp是一个指针变量,它指向一个指针变量,而这个 指针变量又指向一个整型量
#include     <stdio.h>
int main()
{
int     x,*p,**pp;
x=10;
p=&x;
pp=&p;                /*注意赋值方式*/
printf("x=%d,x=%d,x=%d",x,*p,**pp);
}


********************************************************************************************************************
指针终于可以告一段落了
下面对指针进行一个简单的总结:
int     *p;                                                   /*指向整型量的指针变量*/
int     *p[n];                                               /*指针数组,由n个指向整型量的指针元素组成*/
int     (*p)[n];                                            /*指向整型二维数组的指针变量,二维数组列数为n*/
int     *p()                                                    /*返回指针值得函数,该指针指向整型变量*/
int     (*p)()                                                 /*指向函数的指针,该函数返回整型量*/
int     **p                                                    /*指向另一个指针的指针变量,另一个指针指向一个整型量*/

不过下面将继续介绍有关指针的知识
**********************************************************************************************************************

动态内存管理
在前面的C语言程序中,对内存的申请和使用都是通过定义变量的形式实现的,在程序定义变量后由系统为变量申请内存,申请成功后分配内存,
分配内存后在生存期内其地址是固定的,直到生存期结束,系统释放(回收)其占用的内存;
所以这种情况下内存的分配和释放都是系统自动完成的;
但是除此之外,C语言提供了对 内存的动态申请和释放功能,此功能通过 malloc函数和f ree函数来实现

动态内存申请函数malloc()
函数原型:
void     *malloc(unsigned     size );     /*注意void是空类型*/
功能说明:
向系统申请长度为size字节的连续空间;
如果分配成功则返回被分配内存的首地址指针,否则返回空指针NULL;
返回的是 空类型指针,在赋值时要先进行类型转换;
内存不使用时要用free()函数将内存块释放;
calloc函数
(类型说明符*)calloc(n,size)
分配n块size字节连续区域

内存释放函数free()
函数原型:
void     free(void     *block );
功能说明:
如果给定的参数是先前malloc函数返回的指针,那么free()函数会将block所指向的内存空间还给操作系统

在使用上面两个函数时,要添加头文件malloc.h或stdlib.h

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(){
int *p;
p=(int *)malloc(sizeof(int));/*申请一个int型数据所占的内存*/
*p=5;
printf("%d",*p);
free(p); /*释放*/ 
运行结果5

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
int main(){
int *p;
p=(int *)malloc(sizeof(int)*0XFFF);
if(p==NULL)
printf("NO ENOUGH MEMORY!");
else
printf("SUCESS!"); 
运行结果申请成功SUCESS!
但是如果将0XFFF改为0XFFFFFFFFFF则会因为申请内存过大而申请失败

可以定义一个结构体用malloc申请该结构体内存,调用完毕后用free释放从而实现动态内存分配

结构体与共同体

结构体
结构体是一种构造类型,它有若干个”成员“组成,每一个成员都是一个基本数据类型,也可以是一个构造类型,
在说明和使用前需要先定义结构体
定义一般形式:
struct     结构名{
成员表列
};          /*注意后面是有分号的*/
成员表由若干个成员组成,每个成员都是该结构的一个组成部分,对每个成员也必须作类型说明
其形式为:
类型说明符     成员名;

例如:
struct     stu{
    int     num;
    char     name[22];
    char     sex;
    float     score;
};

结构体类型变量的说明
说明结构体类型有三种方法:
以上面stu为例 说明两个变量s1,s2为stu结构类型
(1)先定义结构体,再说明结构体变量
struct     stu{
    int     num;
    char     name[22];
    char     sex;
    float     score;
};
struct     stu     s1,s2;

(2)通过宏定义用一个符号 常量来表示一个结构类型
#define     STU     struct     stu
STU{
    int     num;
    char     name[22];
    char     sex;
    float     score;
};
STU     s1,s2;

(3)在定义结构类型的同时说明结构体变量
struct {
    int     num;
    char     name[22];
    char     sex;
    float     score;
}s1,s2;
第三种方法省去了结构名,而直接给出了结构体变量;
说明s1,s2是stu类型后就可以赋值了

一个结构的成员也可以又是一个结构,即嵌套的结构
struct    data{
int month;
int day;
int year;
}
struct{
int num;
char name[20];
struct date birthday;
float score;
}
其中birthday被说明成data结构
需要说明的是,结构中的成员名可以和程序中其他变量名重名,他们之间互不干扰,因为c语言中访问结构体成员变量和普通变量的方法是不同的

结构体变量成员的引用
在程序中使用结构体变量时,往往不把它当作一个整体来使用,运算一般通过结构体变量的成员来实现
引用结构体变量成员的一般形式:
结构体成员变量.成员名
结构体成员变量的使用和普通变量完全相同

结构体变量的赋值
具有相同类型结构体变量之间可以相互赋值
sturct    stu{
int    num;
char    sex;
}s1,s2;
s1.num=100;
s1.sex=man;
s2=s1;    /*相同类型变量赋值*/

结构体变量的初始化
sturct    stu{
int    num;
char    sex;
};
int    main(){
struct    stu    s1={100,'man'};
struct    stu    s2={100};    /*允许部分赋值,未赋值的分量系统对其自动清零*/
s2.sex=s1.sex;
}

结构体数组
数组元素类型也可可以是数组类型,常用结构体数组来表示具有相同数据结构的一个实体群
结构体数据的定义方法与结构体变量相似,只需要说明他是数组型即可
struct    stu{
int    num;
char    *name;
cahr    sex;
}s[5];
对结构体数组进行初始化赋值
struct    stu{
int    num;
char    *name;
cahr    sex;
}s[5]={
{,,},
{,,},
{,,},
{,,},
{,,},
};
具体数组值不再表示
同样当然如果对全部数组元素进行初始化时,也可以不给出数组长度

结构体指针变量
结构体指针变量的值指向结构体变量的首地址
结构体变量说明的一般形式
(*结构体指针变量). 成员名
或者
结构体指针变量-->成员名/**********注意-->符的使用************/
实际上上面两种表示方法和
结构体变量 . 成员名的效果是一样的

结构体数组指针变量
结构体数组指针变量与数组指针变量类似不再介绍

结构体指针变量做函数参数
当用结构体变量传送函数参数时将会对时间和空间开销很大,严重降低了程序的效率,而运用指针传递就很好地解决了这个问题

共同体
也成为联合体,共同体与结构体类似但又有不同之处,联合体中的成员共享一段内存空间
例如在调查表单位这一项里,学生填的是班级编号(整型)而教师填的是教研室(字符型)
这时就可以定义一个可装入教研室和班级的单位联合体,允许赋值整型和字符型但是,每次只能赋一个值
共同体类型说明:
union    共同体名{
成员表
};
成员表中成员的说明一般是:类型说明符    成员名
例如:
union    udata{
int    class;
char    office[10];
};

共同体变量的说明:
有三种形式,与结构体类似,不再介绍
共同体变量的赋值和使用
共同体变量成员表示为:
共同体变量名 .  成员名
定义完成后,对共同体变量是不能进行初始化的,赋值只能在程序中进行
因为一个共同体变量的值只能是一个,例如:udata类型的a只能进行a . class或者a . office的一个操作,一次只能赋一个值

枚举类型和自定义类型
枚举在定义中要列举出所有可能的取值
定义的一般形式:
enum    枚举名{枚举值}
如:
enmu    weekday{sun,mou,tue,wed,thu,fri,sat};
举变量的说明:
设变量a,b,c是weekday则,
enmu    weekday{sun,mou,tue,wed,thu,fri,sat};
enmu    weekday    a,b,c;
或:
enmu    weekday{sun,mou,tue,wed,thu,fri,sat}a,b,c;
或:
enmu    {sun,mou,tue,wed,thu,fri,sat}a,b,c;

枚举类型是常量,元素本身由系统定义了一个表序号的数值
main()
{
    enmu    weekday{sun,mou,tue,wed,thu,fri,sat}a,b,c;
    a=sun;
    b=mon;
    c=tue;
    printf(“%d,%d,%d”,a,b,c);
}
运行结果是:012
这序号数值无法对enum变量赋值,但可以用强制类型转换进行赋值a=(emue    weekday)2;相当于a=tue;

自定义类型符
typedef
定义的一般形式:
typedef    原类型名    新类型名

typedef    int    INTEGER
typedef定义数组,指针,结构时非常方便
typedef    char    NAME[20];    char    a1[20],a2[20]可以用NAME    a1,a2来代替
typedef    struct    stu
    {
        char    name[20];
        int    age;
        int    sex;
    }STU;
这里直接用STU就可以说明结构变量,STU    body1,body2;这样的形式是很简便的
新类型名一般大写用于区别原类型名,有时也可以用宏定义代替typedef在预处理时完成,不过typedef是在编译时完成的更具有灵活性

逗号运算符和条件运算符
逗号运算符
用逗号将两个或多个表达式连接起来就构成了一个逗号表达式
一般形式:
表达式,表达式
逗号表达式在所有运算符中优先级最低
逗号表达式的运算规则为从左至右依次求解每一个表达式的值,逗号表达式的值是构成该表达式的最后一个表达式的值
下面看一个例子:
3+5,7+9;/*表达式值为16*/
a=2,a++,a+6;/*表达式值为9*/

条件运算符
条件运算符“?:”
一般形式:
表达式1?表达式2:表达式3
运算规则
先求解表达式1,如果表达式1的值为真(非0),则求解表达式2,并将表达式2的值作为整个表达式的值,
如果表达式1的值为假,则求解表达式3,并将表达式3的值作为整个表达式的值;
下面看两个例子:
b=6>7?1:0    /*赋值号后面的6>7?1:0是一个条件含运算符的表达式,因为6>7值为假,所以其值是0,最后赋值b=0*/
(x>y?x:y)>z?(x>y?x:y):z    /*求xyz的最大值*/
另外条件运算符还可以替代一些简单的if语句

位运算和位域
位运算
位运算即按照二进制位进行运算
&    按位与
9&5的运算结果是1,首先把9和5变成二进制数0000 1001和0000 0101再按照对应的每一位进行与运算(同时为1时取1,其他取0),所以得到0000 0001
|      按位或
与上面类似,进行安位或运算9|5结果为13
^    异或
当两个对应位相异时取1,其他取0,9^5为12
~    取反
每一位取反,
<< 左移
把左侧的数的二进制位全部左移若干位,右侧的数指定了移动的位数,3<<4表0000 0011右移四位0011 0000结果是48
>> 右移
把左侧的数的二进制位全部右移若干位,右侧的数指定了移动的位数

位域
在信息存储时,有时并不需要占用一个完整的字节,
位域就是把一个字节的二进制位划分成几个不同的区域,并说明每个区域的位数,每个区域有一个域名,允许程序按域名操作
定义形式:
struct    位域结构名
{位域列表};
其中位域列表的形式为,类型说明符.位域名:位域长度
例:
struct    bs
{
int    a:8;
int    b:2;
};
位域变量的说明和结构变量的说明方式相同
struct    bs
{
    unsigned    a:4    
    unsigned      :0    /*空域,后四位填0表不用*/
    unsigned    b:4    /*从下一个单元存储,b占四位*/
    unsigned    c:4    /*c占四位*/
}
一个位域必须存储在同一个字节中,如果一个字节所剩的空间不够,应该从下一个字节存放该位域,
当然也可以有意的使某个位域从下一个字节开始存放
位域可以无域名,作调整使用,int    :2表这两位不能使用,注意和空域的区别
位域的使用和结构体成员使用相同


C语言操作符优先级

                 C语言操作符优先级

优先级

运算符

    

要求运算

对象的个数

结合方向

1

()

[]

->

.

圆括号

下标运算符

指向结构体成员运算符

结构体成员运算符

 

自左至右

2

!

逻辑非运算符

1

(单目运算符)

自右至左

~

按位取反运算符

++

自增运算符

--

自减运算符

-

负号运算符

(类型)

类型转换运算符

*

指针运算符

&

地址与运算符

sizeof

长度运算符

3

*

/

%

乘法运算符

除法运算符

求余运算符

2

(双目运算符)

自左至右

4

+

-

加法运算符

减法运算符

2

(双目运算符)

自左至右

5

<< 

>> 

左移运算符

右移运算符

2

(双目运算符)

 

6

<=

>=

关系运算符

2

(双目运算符)

自左至右

7

==

!=

等于运算符

不等于运算符

2

(双目运算符)

自左至右

8

&

按位与运算符

2

(双目运算符)

自左至右

9

^

按位异或运算符

2

(双目运算符)

自左至右

10

|

按位或运算符

2

(双目运算符)

自左至右

11

&&

逻辑与运算符

2

(双目运算符)

自左至右

12

||

逻辑或运算符

2

(双目运算符)

自左至右

13

? :

条件运算符

3

(三目运算)

自右至左

14

=

+=

-=

*=

/=

%=

>>=

<<=

&=

^=

|=

赋值运算符

2

自右至左

15

,

逗号运算符

 

自左至右

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值