标准c基础知识六

12年前

写一个宏函数,给一个数组排序,可以通过参数来决定是按照从大到小还是从小到大排序如SORT(a,10,<);SORT(b,8,>)
 
 
 
 //打印出自己
 vi printself.c
 #include <stdio.h>
 
 int main()
 {
  char* s="#include <stdio.h>%cint main()%c{%c%cchar*s=%c%s%c%c%cprintf(s,10,10,10,9,34,10,9,10,9,10,10);%c%creturn 0;%c}%c";
  printf(s,10,10,10,9,34,s,34,10,9,10,9,10,10);
  //换行为10,
  return 0;
 }
 
 
 指针pointer    地址:保存地址的变量
 指向point to  
 
 #include <stdio.h>
 
 
 int main()
 {
  int a[5]={11,22,33,44,55};
  int* p;//野指针,胡乱指向的指针;避免野指针,使用空指针代替
  printf("p=%p\n",p);
  int* q=0;//如果还没让它指哪里可以设置为0;在这里表示空地址,等价于null;空指针;逻辑指针;逻辑地址的假
  //可以写成0,'0',NULL,3>5(逻辑假)
  #define T int*
  union {   //为什么要用联合呢?
   T x;
   char b[sizeof(T)];
  }u;
  
  u.x=q;
  for (i=0;i<sizeof(T);i++){
  
   printf("%d ",u.b[i]);
  }
  printf("\n");
  //*p=100;可能导致非法内存访问
  
  p=a;//数组当作数据用时表示第一个元素的地址;刚好一致
  
  //看以下地址运算
  for (i=0;i<5;i++) printf("%d ",a[i]);printf("\n");
  for(i=0;i<5;i++) printf("%d ",*(a+i));printf("\n");
  for (i=0;i<5;i++) printf("%d ",p[i]);printf("\n");
  for(i=0;i<5;i++) printf("%d ",*(p+i));printf("\n");
  printf("%d\n",(p+2)[1]);//*((p+2)+1)==>*(p+3)==>p[3]
  //在地址运算时,p+1(1在这里代表的是元素的实际长度,如int,32位长度)方括号直接当作加号即可
  q=p+3;//在这里相当于p[3]
  printf("%d\n",1[q]);//1[q]与q[1]是等价的;
  printf("p=%p,q=%p,q-p=%d\n",p,q,q-p);
  for(i=0;i<5;i++) printf("%d ",*p+i);printf("\n");//输出11 12 13 14 15
  for(i=0;i<5;i++) printf("%d ",*p++);printf("\n");//输出11 22 33 44 55
  return 0;
 }
 
 
 深入理解指针,
 vi suspend.c
 #include <stdio.h>
 
 char * func()
 {
  char a='#';
  return &a;//不要返回普通局部变量的地址!
 }
 
 int main()
 {
  char c='@';
  char * p=&c;
  *p='$';
  printf("*p=%c\n",*p);
  p=func();//指向一个可能已被释放的地方,悬空指针
  printf("c=%c\n",c);
  printf("*p=%c\n",*p);//输出可能不是'#'了
  
  return 0;
 
 }
 
 
 
 #include <stdio.h>
 
 void f1(int * p,int *q){int *t=p;p=q;q=t;}
 void f2(int *p,int *q){int t=*p;*p=*q;*q=t;}
 void f3(int a,int b){int t=a;a=b;b=t;}
 
 int main()
 {
  int a=10,b=20;
  //int t=a;a=b;b=t;
  //int *p=&a,q=&b;//警告初始化将指针赋给整数,未作类型转换;在C++中会直接报错
  //如果在一个语句中,定义多个变量时,只有第一个int是公用的。所以最好是按照以下方式来定义
  //int *p=&a;
  //int *q=&b;
  int x,y[5],*p=&a,*q=&b;
  int t=*p;*p=*q;*q=t;//一定要带*
  printf("a=%d,b=%d\n",a,b);
  
  int m=10,n=20;
  int *u=&m,*v=&n;
  int *w=u;u=v;v=w;//u指向n,v指向m;而m和n并没有变
  printf("m=%d,n=%d\n",m,n);
  f1(&m,&n);printf("m=%d,n=%d\n",m,n);//m,n的地址不变
  f2(&m,&n);printf("m=%d,n=%d\n",m,n);//地址传递,通过地址可以间接的访问原始变量
  f3(m,n);printf("m=%d,n=%d\n",m,n);
  //形参永远是实参的复制品;如果是地址传递可以间接的访问原始变量
  return 0;
 }
 
 vi array.c  //看一下,数组作为形参的时候,是真的作为数组吗?
 #include <stdio.h>
 void show(double a[],int n)//a是披着数组皮的指针
 {
  double x=123.45
  printf("sizeof a=%d\n",sizeof(a));
  a=&x;
  printf("*a=%g\n",*a);//由此可以看出是一个指针
 }
 void print(double *p,int n)
 {
  int i;
  for (i=0;i<n;i++)
   printf("%g ",p[i]);
  printf("\n");
 
 }
 int main(0
 {
  double a[5]={1.1,2.2,3.3,4.4,5.5};
  show(a,5);
  print(a,5);
  return 0;
 }
 
 
 *****************************************************************************************
 
 指针就是保存地址的变量;地址只能由指针来保存;指针只能保存地址;
 
 int * k;//表示k是一个变量,是int *类型

 数组名只是用来保存第一个元素的地址
 
 vi string.c
 
 #include <stdio.h>
 #include <string.h>
 int main()
 {
  char a[100]={'h','e','\0','w','o'};
  "helloworld";//const char[11]
  //以上两种方式来表示字符串
  puts(a);
  puts("helloworld");
  char *p=a;
  printf("%c\n",*p);
  *p='w';
  puts(a);
  p="helloword";//有的编译器会警告
  printf("%c\n",*p);//在这里已经输出为c
  //*P='w';//因为前面"helloworld"已经放到只读存储里面,再写的话发生段错误;编译通过运行错误
  //如何解决这个问题呢?
  const char* q=NULL;//*左边的const char表示不会通过指针修改目标的数据;
  q="helloworld";//一定是安全的
  //*q='w';//编译的时候就能检查出错误
  p=a;//在这里也是赋值的地址
  strcpy(a,"nb");
  puts(p);
  q=a+3;//一定是安全的
  //*q='w';//在编译的时候会报向只读位置写入数据
  puts(q);//输出wo
  
  //char const * r;===>等价于const char * r;
  char * const r=a+1;//表示r是一个常量;与上述两种方式不同
  puts(r);
  //r=a;//编译错误
  *r='A';
  puts(a);//输出NA
  char * str;//野指针
  scanf("%s",str);//dangerous
  strcpy(str,"hello");//dangerous
  return 0;
 }
 
 //上例中;q="helloworld";只是把字符串的首地址(第一个字符的地址)赋给了指针;并不是赋值了整个字符串
 //const地方不一样,其含义不一样
 
 
 vi strings.c
 
 #include <stdio.h>
 
 int main()
 {
  char * names[9]={"luko","kooo","look","kogo","hehehe","kaka","lalala","wowo","eeee"};
  int i;
  for (i=0;i<9;i++)
   printf("%s!\n",names[i]);
  const int a=10,b=80,c=20,d=60,e=98,f=76;
  //通过指针来对以上数据排序输出
  const int *p[6]={&a,&b,&c,&d,&e,&f};//每个元素是一个指针,int指针
  int j;
  for (i=0;i<6;i++){
   for(j=j+1;j<6;j++{
    if(*p[j]<*p[i]){
     const int *t=p[j];p[j]=p[i];p[i]=t;//上面在定义指针时用的const
    }
   }
  }
  
  for (i=0;i<6;i++){
   printf("%d ",*p[i]);
  }
  printf("\n");
  return 0;
 }
 
 
 
 
 ls -t -F
 ls -t
 ls -F
 那么我们做的程序如何能类似于ls一样怎么加参数选项呢?
 
 vi cmdline.c
 
 #include <stdio.h>
 int main(int argc,char * argv[])
 //接受命令行字符串的个数,传到main函数;sh会把每个字符串的开始地址存到这个数组里面
 //
 {
  printf("argc=%d\n",argc);
  int i;
  //if(argv[0]!="thanks"){//由于argv[0],"thanks"体现的是地址,要想对比必须采用strcmp
  if(strcmp(argv[0],"thanks")==0){
   printf("请使用正版\n");
  }
  for (i=1;i<argc;i++){
   printf("%d:%s\n",i,argv[i]);
  //注意命令行都是字符串,即使显示的数字"12","34"也是字符串。
  }
  return 0;
 }
 
 
 
 #include <stdio.h>
 
 int main()
 {
  char c;
  char * pc;
  int n;
  int * pn;
  short s;
  short * ps;
  double d;
  double * pd;
  printf("&c=%p\n",&c);
  printf("&pc=%p\n",&pc);
  printf("&n=%p\n",&n);
  printf("&pn=%p\n",&pn);
  printf("&s=%p\n",&s);
  printf("&ps=%p\n",&ps);
  printf("&d=%p\n",&d);
  printf("&pd=%p\n",&pd);
  return 0;
 }
 
 int ** 二级指针
 
 
 #include  <stdio.h>
 #include <ctype.h>
 int str2int(const char* str,const char** q)
 {
  int r=0;
  while(isdigit(*str)){
   r=r*10+*str-'0';
   ++str;
  }
  *q=str;
  return r;
 }
 
 int main()
 {
  const char* p=NULL;
  int n=str2int("3926abxys",&p);
  printf("n=%d,p=%s\n",n,p);
  return 0;
 }
 
 
 
 #include <stdio.h>
 void showbytes(void * addr,int bytes){//void *,没有类型的地址
  while(bytes-->0)
   printf("%02x ",*(unsigned char*)addr++);//%02x,表示不够2位用0来填充
  printf("\n");
  
 }
 int main()
 {
  int n=1234567890;
  float f=1234567890;
  double d=1234567890;
  short s=1234567890;
  //printf("%x,%hx\n",n,s);
  showbyte(&n,sizeof(n));
  showbyte(&n,sizeof(f));
  showbyte(&n,sizeof(d));
  showbyte(&n,sizeof(s));
  return 0;
 }
 
 
 
 
 写一个函数用来检查一个字符串是否是数字字符串。
 isdigitstr("123"):1,isdigitstr("asfw"):0,isigitistr("12f"):0
 写一个函数用来检查一个字符串是否是实数字符串。
 isreal("12.3"):1,isreal("-45"):1,isreal("as"):0,isreal("1.w"):0
 写一个函数用来把一个字符串用指定的字符作为分隔符分割成若干个子串输出
 substr("abc:de:fghi:jk",':')输出
 abc
 de
 fghi
 jk
 写一个函数,用来返回一个字符串中重复出现的最长字串的长度及其开始地址。
 const char *p =NULL;
 int len=maxsubstr("qweohiuweyowohifpw",&p)
 printf("len=%d,substr=%s\n",len,p)
 输出len=3,substr=ohi
 
 指针:类型 *(*号表示什么什么地址的意思)==》合在一起称为一个新的类型
 类型* p()===>表示一个函数
 类型* p[]===>表示一个数组,每个元素是什么什么地址
 类型 *p===>表示一个指针
 
 int *p[5];//p首先是一个数组;5个元素,每个元素是int *类型
 int *p(double);//p是一个函数,形参double,返回类型int *
 int(*p)[5];//p是一个指针,指向5元素的int数组
 
 函数指针---》指向函数的指针
 数组指针--》指向数组的指针
 
 
 vi arrayptr.c
 #include <stdio.h>
 
 int main()
 {
  int * a[5];
  int *f(double);
  int (*p)[5];
  int x[5]={11,22,33,44,55};//x是一个5个元素的int数组
  int m;//m是一个int变量
  int *n;//n是一个指针,指向int
  //n=m;//x
  n=&m;//
  int y[6]={12,23,34,45,56,67};
  int z[8][5];

  //p=x;//x
  p=&x;
  //只有x表示&x[0],int地址;用&x表示数组的地址;一个指针指向数组,表示的是数组的地址,而不是第一个元素的地址
  //int(*)[5]类型
  
  int i;
  for (i=0;i<5;i++){
   printf("%d ",(*p)[i]);
  }
  printf("\n");
  p=&y;//X,&y的类型是int(*)[6],不一致
 
  p=z;//z数组名表示z[0]的地址,是一个int(*)[5]的地址
  
  
  return 0;
  
 }
 
 
 
 
 vi  funcptr.c
 #include <stdio.h>
 
 void funcr(int n)
 {
  printf("你今年%d\n",n);
 }
 void chunge(int n)
 {
  printf("腰围%d\n",n);
 }
 
 
 int main(int argc,char* argv[])
 {
  if(argc<1) return 0;
  int *f(char);//f是一个函数,形参char类型,返回int*类型
  int (*p)(char);//p是一个指针,指向一个形参char返回int的函数
  int(*q)();q是一个指针,指向一个形参任意返回int的函数
  printf("&main=%p\n,%p\n",&main,main);
  //p=&main;//类型不一致
  q=&main;//可以
  (*q)();//一直循环执行main函数
  if (argc>0)
   (*q)(argc-1,argv);
   //q(argc-1,argv);//等价上面;因为函数名就是函数地址
  
  //q=func;//返回类型不一致
  void(*fp)(int n);
  fp=functr;
  fp(18);
  fp=chunge;
  fp(19);
  //以上运行正常
  
  return 0;
 }
 //函数地址是不能进行加减运算的(不能做数学运算,无任何意义)
 //函数地址意义:调用函数和赋值
 //函数名实际上就是函数的地址
 
 
 
 vi foreach.c----
 
 #include <stdio.h>
 void print(int *P){printf("%d ",*p);}
 void add(int *p){*p+=10;}
 void clear(int *p){*p=0;}
 void fill(int *p){
  static int n=0;//初始化值只用一次
  *p=++n;
 }
 void foreach(int a[],int n,void (*fp)(int*))//理解:int a[],int n,void
 {
  int i;
  for(i=0;i<5;i++)
  {
   (*fp)(a+i);//等价于fp(a+i);
  }
 }//为每一个元素做什么事。
 
 
 
 void mul(int *p){*p *=10;}//*p*,这里2个*表示的意思不一样,第一个*通过地址寻找变量,第2个*是表示乘号
 
 int main()
 {
  int a[5];
  int i;
  foreach(a,5,fill);//for(i=0;i<5;i++) fill(a+i);
  foreach(a,5,add);//for(i=0;i<5;i++) add(a+i);
  for(i=0;i<5;i++){
   print(a+i);
  }
  printf("\n");
  for(i=0;i<5;i++) clear(a+i);
  foreach(a,5,mul);
  
  return 0;
 }
 函数指针
 如何做一个通用函数(把另外一个函数作为参数传进来)如上例
 
******************************************************************************** 
 vi sort.c
 #include <stdio.h>
 
 int rule1(double lh,double rh)//要求实现左小右大
 {
  //return(1h>rh);
  if(1h>rh)
   return 1;
  else
   return 0;
 }
 //程序对排序规则的约定:不需要调整顺序就返回0,需要调整顺序就返回1
 
 
 int rule2(double 1h,double rh){return (rh>1h);}//按照从大到小排序
 #define swap(x,y){double t=x;x=y;y=t;}
 void sort(double a[],int n,int (*p)(double,double))
 {
   int i,j;
   for(i=0;i<n;i++)
    for(j=j+1;j<n;j++)
     if(p(a[i],a[j])==1)
      swap(a[i],a[j]);
 }
 //int (*x(void(*p)()))(char);//x是一个函数,其形参为void(*p)();p表示的是一个指针,返回类型void,形参任意
 //等价于以下
 //typedef void (*T)();
 //typedef int (*U)(char);
 //U x(T);
 
 void input(double a[],int n)
 {
  printf("请输入10个小数:");
  int i;
  for(i=0;i<10;i++)
   scanf("%lf",a+i);//&a[i]
 }
 
 void show(double a[],int n)
 {
  int  i;
  for(i=0;i<n;i++)
   printf("%g ",a[i]);
  printf("\n");
 
 }
 int main()
 {
  double a[10];
  input(a,10);
  sort(a,10,&rule1);
  show(a,10);
  sort(a,10,&rule2);
  show(a,10);
  return 0;
 }
 
-------------------------------------------------------------------------------- 
 
 通过指针来访问结构变量
 vi structptr.c
 #include <stdio.h>
 typedef unsigned short int uint16;
 
 typedef struct date{
  //unsigned short int year;
  uint16 year;
  uint16 month;
  uint16 day;
 }date;
 
 void print(date const *p)//使用p可以避免通过p传递数值时不修改其数值
 {
  printf("%d年%d月%d日 ",(*p).year,(*p).month,(*p).day);//为什么*p必须要加上(),主要是因为.号优先级比较()稍微低一点
  //printf("%d年%d月%d日 ",p->year,p->month,p->day);//与上行等价,简化写法
 }
 int main()
 {
  date a[3]={{2011,8,18},{2012,2,13},{2012,5,16}};
  int i;
  for(i=0;i<3;i++)
   print(&a[i]);//或者print(a[i]);
  
  return 0;
 }
 //在C语言中,传递结构变量时总是传递地址
 
 
 通过指针访问堆区
 理解堆区:有借有还再借不难
  栈区:有借无还(容易累积用完了)
  
 malloc分配内存空间
 void * malloc(size_t bytes)
 memset(地址,值,字节数);意思是从某个地址开始多少字节内都设置成某个值//手工清0
 void * calloc(size_t nmemb,size_t size)//会把分配的内存空间清0
 void * realloc(void *ptr,size_t size)//还回旧的,申请新的;void *ptr为旧空间的地址;保证旧空间的数据会复制到新空间中,加长的空间不保证清0
 
 内存的分配和释放一定要配对
 
 内存泄漏 memeory leak
 
 
 vi heap.c
 
 #include <stdio.h>
 #include <stdlib.h>
 int main()
 {
  int b=sizeof(double);
  double * p=(double *)malloc(b);//malloc返回为void,所以必须强制double *
  int cnt;
  printf("请输入元素的个数:");
  scanf("%d",&cnt);
  int * a=calloc(cnt,sizeof(int));//分配一个数组;保证5个元素一定会初始化成0;

  if (a==NULL){
   printf("申请空间失败\n");
   return 1;
  }
  
  printf("p=%p,a=%p\n",p,a);
  *p=123.45;
  int i;
  for(i=0;i<cnt;i++)
   a[i]=i+10;
  printf("%g\n",*p);
  for(i=0;i<cnt;i++)
   printf("%d ",a[i]);
  printf("\n");
  //释放空间
  free(p);
  a=realloc(a,sizeof(int)*10);

  for(i=0;i<10;i++)
   printf("%d ",a[i]);
  printf("\n");
  a=realloc(a,0);
  printf("a=%p",a);
  return 0;
 }
 
 在设计的时候,一定要考虑好内存的分配和释放。
 
 指针本身保存的是地址