c语言基础 2

指针与数组(c语言):
ANSI C使用类型void * (指向void的指针)代替char* 作为通用指针的类型。
地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式,常量或register类型的变量。
我们应该注意,指针只能指向某种特定类型的对象,也就是说,每个指针都必须指向某种特定的数据类型。一个
例外是指向void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。

y = *ip + 1 将把*ip指向的对象的值取出并加1,然后再将结果赋值给y,而下列赋值语句:
*ip += 1  则将ip指向的对象的值加1,它等同于 ++*ip 或者 (*ip)++ 这里的圆括号是必须的,
否则,该表达式将对ip进行加1运算而不是对ip指向的对象进行加1运算
这是因为,类似于*和++这样的一元运算符遵循从右至左的结合顺序。

int a[10];
int *pa;
pa = &a[0];
如果pa指向数组中的某个特定元素,那么,根据指针运算的定义,pa+1将指向下一个元素,
pa+i将指向pa所指向数组元素之后的第i个元素,而pa-i将指向pa所指数组元素之前的第i个元素。
因此,如果指针pa指向a[0],那么*(pa+1)引用的是数组元素a[1]的内容,pa+i是数组元素a[i]的
地址,*(pa+i)引用的是数组元素a[i]的内容。
因为数组名所代表的就是该数组最开始的一个元素的地址,所以,pa = &a[0];也可以写成:
pa = a;
对数组元素a[i]的引用也可以写成*(a+i)这种形式。在计算数组元素a[i]的值时,C语言实际上先将其转换为
*(a+i)的形式,然后再进行求值。
这样 pa[i]与*(pa+i)是等价的。简而言之,一个通过数组和下标实现的表达式可等价的通过指针和偏移量得到。

但是,必须记住的是,数组名与指针间的一个不同之处,指针是一个变量,因此,c中,语句pa =a 和 pa++合法。
但数组名不是变量,因此,类似a = pa 和 a++ 形式的语句是非法的。

记住参数传进去后,在函数体中用的是该参数的一个副本。

在函数定义中,形式参数 char s[]; 和 char *s;等价
如果a是一个数组,那么下面两个函数调用 f(&a[2])与f(a + 2)都将把起始于a[2]的子数组的地址传递给函数f。

C语言保证,0永远不是有效的数据地址,因此,返回值0可用来表示发生了异常事件。

指针与整数不能相互转换,但0是唯一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。 程序中经常
用符号常量NULL代替常量0。

这里有一个特例:指针的算术运算中可使用数组最后一个元素的下一个元素的地址。

有效的指针运算包括 相同类型指针之间的赋值运算;指针同整数之间的加法或减法运算;指向相同数组中元素的两个
指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。  其他所有形式的指针运算都是非法的,例如:
两个指针间的加法,乘法,除法,移位和屏蔽运算;指针同float或double类型之间的加法运算;不经强制类型转换而
直接向一种类型对象的指针赋值给指向另一种类型对象的指针的运算(两个指针之一是void* 类型的情况除外)。

The first function is strcpy(s,t), which copies the string t to the string s. It would be nice just to say s=t but

this copies the pointer, not the characters. To copy the characters, we need a loop. The array version first:

   /* strcpy:  copy t to s; array subscript version */
   void strcpy(char *s, char *t)
   {
       int i;

       i = 0;
       while ((s[i] = t[i]) != '/0')
           i++;
   }

For contrast, here is a version of strcpy with pointers:
   /* strcpy:  copy t to s; pointer version */
   void strcpy(char *s, char *t)
   {
       int i;

       i = 0;
       while ((*s = *t) != '/0') {
           s++;
           t++;
       }
   }

Because arguments are passed by value, strcpy can use the parameters s and t in any way it pleases. Here they are

conveniently initialized pointers, which are marched along the arrays a character at a time, until the '/0' that

terminates t has been copied into s.
In practice, strcpy would not be written as we showed it above. Experienced C programmers would prefer

   /* strcpy:  copy t to s; pointer version 2 */
   void strcpy(char *s, char *t)
   {
       while ((*s++ = *t++) != '/0')
           ;
   }

This moves the increment of s and t into the test part of the loop. The value of *t++ is the character that t

pointed to before t was incremented; the postfix ++ doesn't change t until after this character has been fetched.

In the same way, the character is stored into the old s position before s is incremented. This character is also

the value that is compared against '/0' to control the loop. The net effect is that characters are copied from t to

s, up and including the terminating '/0'.
As the final abbreviation, observe that a comparison against '/0' is redundant, since the question is merely

whether the expression is zero. So the function would likely be written as

   /* strcpy:  copy t to s; pointer version 3 */
   void strcpy(char *s, char *t)
   {
       while (*s++ = *t++)
           ;
   }

Although this may seem cryptic at first sight, the notational convenience is considerable, and the idiom should be

mastered, because you will see it frequently in C programs.


/* strcmp:  return <0 if s<t, 0 if s==t, >0 if s>t */
   int strcmp(char *s, char *t)
   {
       int i;

       for (i = 0; s[i] == t[i]; i++)
           if (s[i] == '/0')
               return 0;
       return s[i] - t[i];
   }

The pointer version of strcmp:
   /* strcmp:  return <0 if s<t, 0 if s==t, >0 if s>t */
   int strcmp(char *s, char *t)
   {
       for ( ; *s == *t; s++, t++)
           if (*s == '/0')
               return 0;
       return *s - *t;
   }

Since ++ and -- are either prefix or postfix operators, other combinations of * and ++ and -- occur, although less

frequently. For example,
   *--p

decrements p before fetching the character that p points to. In fact, the pair of expressions
   *p++ = val;  /* push val onto stack */
   val = *--p;  /* pop top of stack into val */

are the standard idiom for pushing and popping a stack.

strcat(s, t)将t指向的字符串复制到s指向的字符串尾部。
void strcat(char *s, char *t)
{
    while(*s)
        s++;
    while(*s++ = *t++)
         ;
}

strend(s, t) 如果字符串t出现在字符串s的尾部,该函数返回1,否则返回0
int strend(char *s,char *t)
{
   char *bs = s;
   char *bt = t;
   for( ; *s; s++)
       ;
   for( ; *t; t++)
       ;
   for( ; *s == *t; s--, t--)
      if(t == bt || s == bs)
          break;
   if(*s == *t && t == bt && *s != '/0')
       return 1;
   else
       return 0;
}

strncpy: copy n characters from t to s
void strncpy(char *s, char *t, int n)
{
     while(*t && n-- > 0)
         *s++ = *t++;
     while(n-- > 0) 
         *s++ = '/0';   //如果t中字符少于n个,我们将在s的末尾填充“/0”字符
}

strncat: concatenate n characters of t to the end of s
void strncat(char *s, char *t, int n)
{
   void strncpy(char *s, char *t, int n); //声明
   int strlen(char *);
  
   strncpy(s+strlen(s), t, n);
}

strncmp: compare at most n characters of t with s
int strncmp(char *s, char *t, int n)
{
    for( ; *s == *t; s++, t++)
        if(*s == '/0' || --n <= 0)
              return 0;
    return *s - *t;
}

strindex: return index of t in s, -1 if none
int strindex(char *s, char *t)
{
    char *b = s;
    char *p, *r;
    for(; *s; s++)
    {
        for(p = s, r = t; *r != '/0'&& *p == *r; p++, r++)
              ;
        if(r>t && *r == '/0')
            return s-b;  
    }
    return -1;


多维数组:
char daytab[2][13] = {
       {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
       {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
   };
如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数.
数组的行数没有太大关系,因为前面已经讲过,函数调用时传递的是一个指针,它指向由行
向量构成的一维数组,其中每个行向量是具有13个整型元素的一维数组.在该例子中,传递
给函数的是一个指向很多对象的指针,其中每个对象是由13个整型元素构成的一维数组.
因此,如果将数组daytab作为参数传递给函数f,那么f的声明应该写成下列形式:
f(int daytab[2][13])  {...  }
也可以写成
f(int daytab[][13]) {... }
因为数组的行数无关紧要,所以,该声明还可以写成
f(int (*daytab)[13])  {... }
这种形式声明参数是一个指针,它指向具有13个整型元素的一维数组。因为方括号[]的
优先级高于*的优先级,所以上诉声明中必须使用圆括号。如果去掉括号,则声明变成
int *daytab[13]
这相当于声明了一个数组,该数组有13个元素,其中每个元素都是一个指向整型对象的指针。
一般来说,除数组的第一维(下标)可以不指定大小外,其余各维都必须明确指定大小。

指针数组初始化语法:
/* month_name:  return name of n-th month */
   char *month_name(int n)
   {
       static char *name[] = {
           "Illegal month",
           "January", "February", "March",
           "April", "May", "June",
           "July", "August", "September",
           "October", "November", "December"
       };

       return (n < 1 || n > 12) ? name[0] : name[n];
   }
由于声明中没有指明name的长度,因此,编译器编译时将对初值个数进行统计,并将这一准确数字填入数组的长度。

不要混淆二维数组和指针数组 有区别
int a[10][20]
int *b[10]
从语法角度讲,a[3][4]和b[3][4]都是对一个int对象的合法引用。但a是一个真正的二维数组,它分配了200个int类型
长度的存储空间,并且通过常规的矩阵下标计算公式20*row+rol(其中row表示行,col表示列)计算得到元素a[row][col]
的位置。但是,对b来说,该定义仅仅分配了10个指针,并且没有对它们初始化,它们的初始化必须以显式的方式进行,
比如静态初始化或通过代码初始化。假定b的每个元素都指向一个具有20个元素的数组,那么编译器就要为它分配200个int
类型长度的存储空间以及10个指针的存储空间。指针数组的一个重要优点在于,数组的每一行长度可以不同,也就是说,b
的每个元素不必都指向一个具有20个元素的向量,某些元素可以指向具有2个元素的向量,某些元素可以指向50个元素的
向量,而某些元素可以不指向任何向量。

命令行参数:
例: 输入回显命令 echo hello, world 则将打印下列输出: hello,world
按照c语言的约定,argv[0]的值是启动该程序的程序名,因此argc的值至少为1。如果argc的值为1,则说明程序名后面没有命令
行参数。在上面的例子中,argc的值为3,argv[0],argv[1],argv[2]的值分别为"echo", "hello, ", "world".第一个可选参数
是argv[1],而最后一个可选参数是argv[argc - 1]. 另外ANSI标准要求argv[argc]的值必须为一空指针。
   #include <stdio.h>

   /* echo command-line arguments; 2nd version */
   main(int argc, char *argv[])
   {
       while (--argc > 0)
           printf("%s%s", *++argv, (argc > 1) ? " " : "");
       printf("/n");
       return 0;
   }

指向函数的指针:
在c语言中,函数本身不是变量,但可以定义指向函数的指针.这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的
返回值等等.

   /* qsort:  sort v[left]...v[right] into increasing order */
   void qsort(void *v[], int left, int right,
              int (*comp)(void *, void *))
   {
       int i, last;

       void swap(void *v[], int, int);

       if (left >= right)    /* do  nothing if array contains */
           return;           /* fewer than two elements */
       swap(v, left, (left + right)/2);
       last = left;
       for (i = left+1; i <= right;  i++)
           if ((*comp)(v[i], v[left]) < 0)
               swap(v, ++last, i);
       swap(v, left, last);
       qsort(v, left, last-1, comp);
       qsort(v, last+1, right, comp);
   }

The declarations should be studied with some care. The fourth parameter of qsort is
   int (*comp)(void *, void *)

which says that comp is a pointer to a function that has two void * arguments and returns an int.
The use of comp in the line

   if ((*comp)(v[i], v[left]) < 0)

is consistent with the declaration: comp is a pointer to a function, *comp is the function, and
   (*comp)(v[i], v[left])

is the call to it. The parentheses are needed so the components are correctly associated; without them,
   int *comp(void *, void *)    /* WRONG */

says that comp is a function returning a pointer to an int, which is very different.

负责声明:
C语言常常因为声明的语法问题而受到人们的批评,特别是涉及到函数指针的语法. C语言的语法力图使声明和使用相一致.
对于简单的情况,c语言的做法很有效,但是,如果情况复杂,则易让人混淆,原因在于,c的声明不能从左到右阅读,而且使用
太多的圆括号.
比如:
char **argv   //argv是一个char型指针
int (*daytab)[13]  //daytab是一个指针,它指向具有13个整型元素的一维数组
int  *daytab[13]   //daytab是一个数组,数组中有13的元素,每个元素是一个指向整型对象的指针
void *comp()       //comp 是一个函数,该函数返回一个指向void型对象的指针
void (*comp)()     //comp 是一个指向函数的指针,这个函数没有参数,返回void
char (*(*x())[])() //首先是个函数,它返回一个指针,这个指针指向一个数组,这个数组里的内容是返回char的函数指针
                   //x: function returning pointer to array[] of pointer to function returning char

char (*(*x[3])())[5]  //x: array[3] of pointer to function returning pointer to array[5] of char
解析可以用右左法则 见我转的csdn博文

结构:
声明
   struct example {
       int len;
       char *str;
   }; //example是它的结构标记 声明或定义时可代替以下部分
       {
         int len;
         char *str;
       }

所以声明一个指向struct的指针 可以
   struct {
       int len;
       char *str;
   } *p;
也可以 struct example *p;

stuct 声明了一种数据类型。 声明的时候就分配了存储空间
结构初始化可以在定义的后面使用初值表进行。如:struct point maxpt = {320, 200};

上面定义的p  表达式 ++p->len 将增加len的值,而不是增加p的值。 因为->的优先级高。
(++p)->len将先执行p的加1操作,再对len执行操作 ,而(p++)->len,则对len执行操作,然后再将
p加1(该表达式中括号可以省略)。

同样的道理,*p->str读取的是指针str所指对象的值,*P->str++先读取指针str指向的对象的值,
然后再将str加1(与*s++相同),(*p->str)++将指针str指向的对象的值加1,*p++->str先读取
指针str所指对象的值,然后再将p加1。因为像++这样的一元操作符从右向左结合 再考虑一些优先级,这些式子还是
比较好理解的。

c语言提供了一个编译时(compile-time)一元运算符sizeof,它可用来计算任一对象的长度。
它返回一个整型,等于指定对象或类型占用的存储空间字节数。
条件编译语句#if中不能使用sizeof,因为预处理器不对类型名进行分析。但预处理器
并不计算#define语句中的表达式,因此,在#define中使用sizeof是合法的。

对算法的最重要的修改在于,要确保不会生成非法的指针,或是试图访问数组范围之外的元素。问题在于,
&tab[-1]和&tab[n]都超出了数组tab的范围。前者是绝对非法的,而对后者的间接引用也是非法的。但是,
c语言的定义保证数组末尾之后的第一个元素(即&tab[]的指针算术运算可以正确执行)

注意: 千万不要认为结构的长度等于各成员长度的和。因为不同的对象有不同的对齐要求,所以,结构中
可能会出现未命名的"空穴"(hole).例如,假设char类型占用一个字节,int类型占用4个字节,则下列结构
struct{
    char c;
    int i;
      };
可能需要8个字节,而不是5个,使用sizeof可以返回正确的对象长度。

指向结构的指针
自引用结构 典型的例子就是二叉树 以结构表示一个节点 而改节点包括左右节点的指针 具体见二叉树对
输入单词按字典顺序排序

表查找 散列实现
比如 #define IN 1 就需要把IN和替换文本1 存入 某个表中,而在程序中出现IN时,再到表中查找,并用1替换它

typedef

联合 联合是可以(在不同时刻)保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对齐要求。联合
提供了一种方式,以在单块存储区中管理不同类型的数据,而不需要在程序中嵌入任何同机器有关的信息。

位字段

printf
变长参数表

目录列表 存储分配 typedef和联合解决对齐问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值