C学习笔记(六)函数、数组与指针

来源:互联网 发布:用四元数计算旋转矩阵 编辑:程序博客网 时间:2024/05/21 08:51

一、数组

数组元素不可以是函数类型或者不完整类型
C不允许把数组作为一个整体进行赋值

固定长度的数组

    固定长度的数组可以具有任何的存储类别,可以将它们定义在函数外面或语句块的里面。

长度可变的数组

    结构或联合的成员不能是长度可变的数组
变长数组没有静态存储期限,没有初始化式

void func(int n){    int vla[2*n]     //合法,生存周期是自动的    static int e[n] //非法,长度可变的数组无法具有静态生存周期}

初始化数组

int powers[8] = {1,2,3,4,5,6,7,8};
const int days[8] = {1,2,3,4,5,6,7,8}; //只读数组
也可以省略括号里的数字
const int days[ ] = {1,2,3,4,5,6,7,8};

    const int days[] = {1,2,3,4,5,6,7,8};    int index;    for( index = 0;index < sizeof days /sizeof days[0];index++)    {            printf("sizeof(days)=%d字节,days[%d] =   %d\n",sizeof(days),index,days[index]);    }    ------------------------    sizeof(days)=32字节,days[0] = 1    sizeof(days)=32字节,days[1] = 2    ...

2.指定初始化项目
传统的const int days[] = {1,2,3,4,5,6,7,8};
指定一个值
const int days[8] = {1,2,3,4,5,6,7,[1]=444};

    int  days[12] ={12,23,[4]=28,4,5,6,[1]=17};    int i=0;    for(i=0;i<12;i++){        printf("days[%d] = %d\n",i,days[i]);    }    ---------------------------------------------    days[0] = 12    days[1] = 17    days[2] = 0    days[3] = 0    days[4] = 28    days[5] = 4    days[6] = 5    days[7] = 6    days[8] = 0    days[9] = 0    days[10] = 0    days[11] = 0

二、指针

使用指针前,一定要初始化指针。

指针:用来存储地址的变量,代表一个”对象或函数“的”地址和类型“,
声明指针时一定要声明其类型

指针与限定符const 、volatile、restrict

auto、register、static、extern是属于存储类修饰符。在声明时,存储类修饰符最多只能使用一个,而且无法用在typeof声明中。

而类型限定符是指const、volatile、restrict。声明中可以使用多个类型限定符,顺序没有限制。另外,类型限制符可以用在typeof声明中。

const
对象的类型如果有const限定符,该对象就是常量;在定义该对象之后,就无法修改它。

volatile
对象的类型如果有volatile限定符,就可能会被其他执行程序或事件所修改。volatile关键字告诉编译器在每次使用此对象的值时,都要重新读取,即使程序本身并没有修改它的值。

restrict
restrict限定符只适用于对象指针类型。这是C99新增加的,用来告诉编译器,此指针所指向的对象如果被修改,就不可以被此指针以外的方式所存取,不管是直接地还是间接地。但是没有强制实施最佳优化,编译器可能会对它置之不理。

空指针

当把空指针常量转换为指针类型,所得结果就是空指针,空指针常量是一个值为0的整数常量表达式(NULL)

void指针(void *)

void *被成为王能指针类型。就是说void指针可以代表任何对象的地址,但不代表该对象的类型。如果想存取内存内的对象,必须先把void 指针转换为合适的对象指针。
声明一个可以接收任何类型指针自变量函数

void * memset(void * s,int c,size_t n);

编译器会在必要的地方把void指针转换为对象指针。

int * iptr = malloc(1000 * sizeof(int));

数组指针和指针数组

数组指针(也称行指针)int (*p)[n]

int (* arrPtr) [10];int matrix[3][10];int array[10];arrPtr = matrix;(*arrPtr)[0] = 5;    //将第一行的第一个元素赋值为5arrPtr[2][9] = 6;    //将最后一行的最后一个元素赋值为6++arrptr             //指针移动到下一行//arrPtr = array; 错误,指针类型不符合arrPtr = (int(*)[10])array;  //类型转换

int a[3][4];
int (*p)[4];
p = a;
p++等同于a[1],也等同于p[1]

   p[i][j]   a[i][j]   *(p[i]+j)   *(a[i]+j)   *(*(p+i)+j)   *(*(a+i)+j)   (*(p+i))[j]   (*(a+i))[j]   都是一样的

指针数组 int *p[n]

     int *point[3];     int array[3][4] = {100,101,102,104,105,106,107,108,109,110,111,112}  ;     int i;     int j;     for(i = 0; i < 3; i++)     {         point[i] = array[i];     }     for(i = 0; i < 3; i++)     {         printf("array[%d] = %#p\n ",i,array[i]);         printf("point[%d] = %#p\n ",i,point[i]);         for(j=0;j<4;j++)         {             printf("*(point[%d]+%d )= %d\n ",i,j,*(point[i]+j));         }         puts("");     }     -------------------------------------------------------------- array[0] = 0X0028FEE0 point[0] = 0X0028FEE0 *(point[0]+0 )= 100 *(point[0]+1 )= 101 *(point[0]+2 )= 102 *(point[0]+3 )= 104 array[1] = 0X0028FEF0 point[1] = 0X0028FEF0 *(point[1]+0 )= 105 *(point[1]+1 )= 106 *(point[1]+2 )= 107 *(point[1]+3 )= 108 array[2] = 0X0028FF00 point[2] = 0X0028FF00 *(point[2]+0 )= 109 *(point[2]+1 )= 110 *(point[2]+2 )= 111 *(point[2]+3 )= 112

下面到底哪个是数组指针,哪个是指针数组呢:
A) int *p1[10];
B) int (*p2)[10];

这里写图片描述

函数指针和指针函数

函数指针
指向函数的指针变量没有++和- -运算

    extern double pow(double,double);    double (*funcPtr) (double,double);    funcPtr = pow;    double result;    result = (*funcPtr)(3,2);   //调用funcPtr所指向的函数    result = funcPtr(3,2)       //和上面的一样
 #include  <stdio.h>extern double pow(double,double);double Add(double x,double y){return x+y;}double Sub(double x,double y){return x-y;}double Mul(double x,double y){return x*y;}double Div(double x,double y){return x/y;}int main(void) {    double (*funcPtr[5])(double ,double) = {Add,Sub,Mul,Div,pow}; //5个函数指针的数组    int i;    for(i=0;i<5;i++){        printf("%3.2f\n",funcPtr[i](2,2));    }    return 0;}

对于函数指针这样的复杂类型,可以使用typedef来赋予新名称,会比较容易管理,
typedef double func_t(double,double)
func_t * funcPtr[5] = {Add,Sub,Mul,Div,pow};
指针函数
指针函数是指返回值是指针的函数,即本质是一个函数。我们知道函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
其定义格式如下所示:
返回类型标识符 * 函数名称(形式参数表)
{函数体}
返回类型可以是任何基本类型和复合类型。

常量指针和指针常量

常量指针是 只读指针。

int var;int one;const int * ptr = &var;  //const 修饰int,ptr 指向const  int///或者int const  * ptr = &var;*ptr = 12;              //错误ptr = &one;             //合法

指针常量

int var;int * const ptr = &var;  //const 修饰ptr*ptr = 12;              //合法,可以修改指向的对象++ptr;                  //错误,我们不能修改此指针 
int var;const  int * const ptr = &var;  *ptr = 12;              //错误++ptr;                  //错误

注意:
常量或非常量数据的地址赋给指向常量的指针是合法的;
但是只有非常量数据的地址才可以赋给普通指针,这样才可以通过普通指针修改非常量的数据

指针运算

使用指针来读取和修改对象
如果ptr 是一个指针,那么*ptr就是ptr所指向的对象或函数

double x,y *ptr;ptr = &x;*ptr = 7.8;         // x = 7.8*ptr *= 2.5;       //x= x*2.5y = *ptr + 0.5;   // y = x+0.5 
对于指向对象的指针(因为还有指向函数的指针) - 对一个指针执行整数加法和减法操作 - 两个指针相减 - 比较两个指针####指针的指针###三、函数任何程序在使用函数之前都需要声明该函数的类型。 当函数接收参数时,声明时可以省略变量名如:void show(char ch,int num)可以写成void show(char,int)register 是唯一可以修饰函数参数的存储类修饰符####数组当作函数参数当数组的名字出现在函数自变量中,编译器会将其隐式地转换为“指向数组第一个元素”的指针。对应的函数参数也必须是指针,所指向的类型和此数组元素的类型相同。类型 名称[ ] 等同于 类型 * 名称
void addArray(register float a1[ ],register const float a2 [ ],int len){....}
void addArray(register float *a1,register const float * a2 , int len){....}

在参数声明中,C99允许将类型限定符const、volatile、register放在方括号内,以便建立“具有限定符指针类型”的参数
在C99中,也可以将存储类修饰符static和整数常量表达式一起放在方括号内。表示,在函数调用的时候,数组内元素的个数至少会等于此常量表达式。

//函数参数是一个指向long的指针,不能被修改,它指向一个数组,该数组至少有5个元素int func(long array[ const static 5]){......}

这样使用static不会对程序的行为有任何影响。static的存在只不过是一个”提示”,C编译器可以据此生成更快的指令来访问数组。(如果编译器知道数组总是具有某个最小值,那么它可以在函数调用时预先从内存中取出这些元素值,而不是在遇到函数内部实际需要用到这些元素的语句时才取出相应的值。)

最后,关于static还有一点值得注意:如果数组参数是多维的,static仅可用于第一维(例如,指定二维数组的行数。)

在C99中参数声明可以包含长度可变的数组。
C99增加了几个与数组型参数相关的特性。第一个是变长数组,这一特性允许我们用非常量表达式指定数组的长度。变长数组也可以作为参数。

考虑本节前面提到过的函数sum_array,这里给出它的定义,省略了函数体部分:

int sum_array(int a[], int n)  {    ...  } 
这样的定义使得n和数组a的长度之间没有直接的联系。尽管函数体会将n看作数组a的长度,但是数组的实际长度有可能比n大(也可能小,这种情况下函数不能正确运行)。如果使用变长数组形式参数,我们可以明确说明数组a的长度就是n:
int sum_array(int n, int a[n])  {    ...  } 
第一个参数(n)的值确定了第二个参数(a)的长度。注意,这里交换了形式参数的顺序,使用变长数组形式参数时参数的顺序很重要。下面的sum_array函数定义是非法的:
int sum_array(int a[n], int n)        //错误{    ...  } 
编译器会在遇到int a[n]时显示出错消息,因为此前它没有见过n。对于新版本的sum_array函数,其函数原型有好几种写法。一种写法是使其看起来跟函数定义一样:
int sum_array(int n, int a[n]);     /* Version 1 */ 另一种写法是用*(星号)取代数组长度:  int sum_array(int n, int a[*]);     /* Version 2a */ 

使用*的理由是:函数声明时,形式参数的名字是可选的。如果第一个参数定义被省略了,那么就没有办法说明数组a的长度是n,而星号的使用则为我们提供了一个线索–数组的长度与形式参数列表中前面的参数相关:

int sum_array(int, int [*]);            /* Version 2b */  
另外,方括号中为空也是合法的。在声明数组参数中我们经常这么做:
int sum_array(int n, int a[]);      /* Version 3a */ int sum_array(int, int []);         /* Version 3b */ 

但是让括号为空不是一个很好的选择,因为这样并没有说明n和a之间的关系。

一般来说,变长数组形式参数的长度可以是任意表达式。例如,假设我们要编写一个函数来连接两个数组a和b,要求先复制a的元素,再复制b的元素,把结果写入第三个数组c:

int concatenate(int m, int n, int a[m], int b[n], int c[m+n])      {     ...  } 

数组c的长度是a和b的长度之和。这里用于指定数组c长度的表达式只用到了另外两个参数;但一般来说,该表达式可以使用函数外部的变量,甚至可以调用其他函数。

到目前为止,我们所举的例子都是一维变长数组形式参数,变长数组的好处还体现得不够充分。一维变长数组形式参数通过指定数组参数的长度使得函数的声明和定义更具描述性。但是,由于没有进行额外的错误检测,数组参数仍然有可能太长或太短。

如果变长数组参数是多维的则更加实用。之前,我们尝试过写一个函数来实现二维数组中元素相加。原始的函数要求数组的列数固定。如果使用变长数组形式参数,则可以推广到任意列数的情况:

int sum_two_dimensional_array(int n, int m, int a[n][m])  {    int i, j, sum = 0;    for (i = 0; i < n; i++)      for (j = 0; j < m; j++)        sum += a[i][j];    return sum;  }  这个函数的原型可以是以下几种:  int sum_two_dimensional_array(int n, int m, int a[n][m]);  int sum_two_dimensional_array(int n, int m, int a[*][*]);  int sum_two_dimensional_array(int n, int m, int a[][m]);  int sum_two_dimensional_array(int n, int m, int a[][*]); 

参考:
http://www.cnblogs.com/mq0036/p/3382732.html
http://book.51cto.com/art/201002/182919.htm

0 0