C语言——指针与数组(下)

来源:互联网 发布:软件 开发 详细价格 编辑:程序博客网 时间:2024/05/19 00:14

一、指针数组与数组指针的分析

思考:

# 下面这些声明合法吗?

int array[5];

int matrix[3][3];

int * pA = array;

int * pM = matrix;

array代表数组首元素的地址,而matrix代表一维地址,也就是数组第一行的行地址。

array和&array的地址值相同,但意义不同,在指针运算时表现不同,例如,array + 1代表数组的第二个元素,而&array + 1代表数组最后元素的后面一个元素,我们不能对其访问,否则,属于越界访问。

1、数组的类型

# C语言中的数组有自己特定的类型

# 数组的类型由元素类型和数组大小共同决定:int array[5]的类型为int[5]

# C语言中通过typedef为数组类型重命名

typedef type(name)[size];

# 数组类型:

typedef int(AINT5)[5];

typedef float(AFLOAT10)[10];

# 数组定义:

AINT5 iArray;

AFLOAT10 fArray;

2、数组指针

# 数组指针用于指向一个数组

# 数组名是数组首元素的起始地址,但并不是数组的起始地址

# 通过将取地址符&作用于数组名可以得到数组的起始地址

# 可通过数组类型定义数组指针:Array Type* pointer;

# 也可以直接定义:type(*pointer)[n];

pointer为数组指针变量名

type为指向的数组的类型

n为指向的数组的大小

实例分析:

数组类型的定义

数组指针的使用

数组指针运算

#include <stdio.h>

typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
typedef char(ACHAR9)[9];

int main()
{
    AINT5 a1;
    float fArray[10];
    AFLOAT10* pf = &fArray;
    ACHAR9 cArray;
    char(*pc)[9] = &cArray; //pc是一个指针 
    //char(*pcw)[4] = cArray; // cannot convert 'char*' to 'char(*)[4]' in initialization    
    //char(*pcw)[4] = &cArray; //cannot convert 'char(*)[9]' to 'char(*)[4]' in initialization
    char(*pcw)[9] = &cArray;
    int i = 0;
    
    printf("%d, %d\n", sizeof(AINT5), sizeof(a1));
    
    for(i=0; i<10; i++)
    {
        (*pf)[i] = i;
    }
    
    for(i=0; i<10; i++)
    {
        printf("%f\n", fArray[i]);
    }
    
    printf("%0X, %0X, %0X\n", &cArray, pc+1, pcw+1);
    //pc+1 ==  (unsigned int)pc + 1*sizeof(*pc)
    //pc是数组指针,*pc指向含有9个char类型元素的地址,所以这里sizeof(*pc)的结果为9 
}

运行结果:

20, 20
0.000000
1.000000
2.000000
3.000000
4.000000
5.000000
6.000000
7.000000
8.000000
9.000000
22FF23, 22FF2C, 22FF2C
3、指针数组

# 指针数组是一个普通的数组

# 指针数组中每个元素为一个指针

# 指针数组的定义:type * pArray[n];

type*为数组中每个元素的类型

pArray为数组名

n为数组大小
指针数组的使用,关键字查找

#include <stdio.h>
#include <string.h>

int lookup_keyword(const char* key, const char* table[], const int size)
{
    int ret = -1;
    
    int i = 0;
    
    for(i=0; i<size; i++)
    {
        if( strcmp(key, table[i]) == 0 )
        {
            ret = i;
            break;
        }
    }
    
    return ret;
}


#define DIM(a) (sizeof(a)/sizeof(*a))

int main()
{
    const char* keyword[] = {
            "do",
            "for",
            "if",
            "register",
            "return",
            "switch",
            "while",
            "case",
            "static"
    };
    
    printf("%d\n", lookup_keyword("return", keyword, DIM(keyword)));
    printf("%d\n", lookup_keyword("main", keyword, DIM(keyword)));
}

运行结果:

4

-1

4、main函数的参数

# main函数可以理解为操作系统调用的函数

# 在执行程序的时候可以向main函数传递参数

int main()

int main(int argc)

int main(int argc, char *argv[])

int main(int argc, char *argv[], char *env[])

argc-命令行参数个数

argv-命令行参数数组

env-环境变量数组

实例代码:

#include <stdio.h>

int main(int argc, char* argv[], char* env[]) //char *env[]操作系统的环境变量 
{
    int i = 0;
    
    printf("============== Begin argv ==============\n");
    
    for(i=0; i<argc; i++)
    {
        printf("%s\n", argv[i]);
    }
    
    printf("============== End argv ==============\n");
    
    printf("\n");
    printf("\n");
    printf("\n");
    
    printf("============== Begin env ==============\n");
    
    for(i=0; env[i]!=NULL; i++)
    {
        printf("%s\n", env[i]);
    }
    
    printf("============== End env ==============\n");
}

小结:

# 数组指针本质上是一个指针

# 数组指针指向的值是数组的地址

# 指针数组本质上是一个数组

# 指针数组中每个元素的类型是指针

二、多维数组和多维指针

1、指向指针的指针

# 指针变量在内存中会占用一定的空间

# 可以定义指针来保存指针变量的地址值

int main()

{

int a = 0;

int *p = NULL;

int **pp = NULL;

pp = &p;

*pp = &a;

return 0;

}

# 为什么需要指向指针的指针?

1、指针在本质上也是变量

2、对于指针也同样存在传值调用与传址调用

实例代码:多级指针的分析与使用,重置动态空间大小

#include <stdio.h>
#include <malloc.h>

int reset(char**p, int size, int new_size)
{
    int ret = 1;
    int i = 0;
    int len = 0;
    char* pt = NULL;
    char* tmp = NULL;
    char* pp = *p;
    
    if( (p != NULL) && (new_size > 0) )
    {
        pt = (char*)malloc(new_size);
        
        tmp = pt;
        
        len = (size < new_size) ? size : new_size;
        
        for(i=0; i<len; i++)
        {
            *tmp++ = *pp++;      
        }
        
        free(*p);
        *p = pt;
    }
    else
    {
        ret = 0;
    }
    
    return ret;
}

int main()
{
    char* p = (char*)malloc(5);
    
    printf("%0X\n", p);
    
    if( reset(&p, 5, 3) )
    {
        printf("%0X\n", p);
    }
    
    return 0;
}

2、二维数组与二级指针

# 二维数组在内存中以一维的方式排布

# 二维数组中的第一维是一维数组

# 二维数组中的第二维才是具体的值

# 二维数组的数组名可看做常量指针

实例代码:以一维的方式遍历二维数组:

#include <stdio.h>
#include <malloc.h>

void printArray(int a[], int size)
{
    int i = 0;
    
    printf("printArray: %d\n", sizeof(a));

    for(i=0; i<size; i++)
    {
        printf("%d\n", a[i]);
    }
}

int main()
{
    int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    int* p = &a[0][0];
    
    printArray(p, 9);
    
    return 0;
}

运行结果:

printArray: 4
0
1
2
3
4
5
6
7
8

思考:int matrix[2][4];

matrix到底是2行4列还是4行2列?

答:都对,因为你如果把二维数组的第一维当做行,那么访问时就是按照行访问,反之就是按列来访问。而这些都不会影响二维数组用来存放数据,只要你访问时按照同一方法,就可以。

3、数组名

# 一维数组名代表数组首元素的地址

int a[5] -> a的类型为int*

# 二维数组名同样代表数组首元素的地址

int m[2][5] ->m的类型为int(*)[5]

结论:

1、二维数组名可以看做是指向数组的常量指针

2、二维数组可以看做是一维数组

3、二维数组中的每个元素都是同类型的一维数组

#include <stdio.h>

int main()
{
    int a[5][5];
    //int(*p)[4]; //cannot convert int[5][5] to int(*)[4] in assignment
    int(*p)[5]
    
    p = a;
    
    printf("%d\n", &p[4][2] - &a[4][2]);
}

运行结果为0,如果用 int(*p)[4]替换后结果为-4

实例代码:

以指针的方式遍历二维数组:

#include <stdio.h>

int main(int argc, char* argv[], char* env[])
{
    int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    int i = 0;
    int j = 0;
    
    for(i=0; i<3; i++)
    {
        for(j=0; j<3; j++)
        {
            printf("%d\n", *(*(a+i) + j));
        }
    }
}

运行结果:

0

1

2

3

4

5

6

7

8

如何动态申请二维数组:

程序一:

#include <stdio.h>
#include <malloc.h>

int ** malloc2d(int row, int col)
{
int ** ret = (int **)malloc(sizeof(int *) * row);
int *p = (int *)malloc(sizeof(int) * row *col);

int i = 0;
if (ret && p)
{
for(i=0; i<row; i++)
{
ret[i] = (p + i * col);
}
}
else
{
free(ret);
free(p);
 
ret = NULL;
}

return ret;
}
void free2d(int **a)
{
free(a[0]);
free(a);
}
int main()
{

return 0;
}
程序二:

#include <stdio.h>  
#include <stdlib.h>  
#include <malloc.h>
#define Malloc(type,n)  (type *)malloc((n)*sizeof(type))  
  
int main(int argc, char **argv)  
{  
    int **array;  
    int i,j,row, column;  
    if(argc!=3)  
    {  
        printf("Run me with 2 parameters--rows & columns. For example:\n%s 3 4\nreturns 3 rows 4 columns array\n", argv[0]);  
        exit(1);  
    }  
    row = atoi(argv[1]);  
    column = atoi(argv[2]);  
    array=Malloc(int *, row);  
    for(i=0;i<row;i++)  
    {  
        array[i]=Malloc(int, column);  
    }  
    //validation  
    for(i=0;i<row;i++)  
    {  
        for(j=0;j<column;j++)  
        {  
            array[i][j]=i;  
            printf("%4d", array[i][j]);  
        }  
        printf("\n");  
    }  
    for(i=0;i<row;i++)  
        free(array[i]);  
    free(array);  
    return 0;  
}  
小结:

1、C语言中只有一维数组,而且数组大小必须在编译期就作为常数确定

2、C语言中的数组元素可是任何类型的数据,即数组的元素可以是另一个数组
3、C语言中只有数组的大小和数组首元素的地址是编译器直接确定的

三、数组参数和指针参数分析

1、退化的意义

# C语言中只会以值拷贝的方式传递参数

# 当向函数传递数组时可以采用两种方式:1、将整个数组拷贝一份传入函数2、将数组名看做常量指针传数组首元素地址

显然C语言采用的是第二种方法,原因是,C语言在设计之初就是以高效为最初设计目标(这也是C可以编写操作系统的原因),在函数传递的时候如果拷贝整个数组执行效率将大大下降。

2、二维数组参数

2.1、二维数组参数同样存在退化的问题

二维数组可以看做是一维数组

二维数组中的每个元素是一维数组

2.2、二维数组参数中第一维的参数可以省略

void f(int a[5]); <--> void f(int a[]); <- -> void f(int *a);

void g(int a[3][3]); <--> void g(int a[][3]); <--> void g(int (*a)[3]);


注意事项:

1、C语言中无法向一个函数传递任意的多维数组

2、为了提供正确的指针运算,必须提供除第一维之外的所有维长度

3、限制:一维数组参数-必须提供一个标示数组结束位置的长度信息;二维数组参数-不能直接传递给函数;三维或更多维数组参数-无法使用

代码分析:

#include <stdio.h>#include <stdio.h>//void f(int p[][3])//不能挖剩余这个3 ,因为如果没有3就无法进行指针运算 void f(int (*p)[3])//数组指针两部分:数据类型和数组大小 {printf("sizeof(p) = %d\n", sizeof(p));printf("%d\n", p[1][1]);printf("%d\n", *(*(p + 1) + 1));}int main(){int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};int b[2][2];f(a);//f(b);//保错,因为不兼容。也说明了不能传递二维数组给函数}
运行结果:

sizeof(p) = 4
4
4

传递和访问二维数组的方式:

#include <stdio.h>void access(int a[][3], int row){    int col = sizeof(*a) / sizeof(int);//一维数组的大小     int i = 0;    int j = 0;        printf("sizeof(a) = %d\n", sizeof(a));        for(i=0; i<row; i++)    {        for(j=0; j<col; j++)        {            printf("%d\n", a[i][j]);        }    }}int main(){    int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};        access(a, 3);}

四、函数与指针分析

1、C语言中的函数有自己特定的类型

2、函数的类型由返回值,参数类型和参数个数共同决定

例:int add(int i, int j)的类型为int(int, int)

3、C语言中通过typedef为函数类型重命名

typedef type name(parameter list)

例:

typedef int f(int , int);

typedef void p(int);

4、函数指针

4.1、函数指针用于指向一个函数

4.2、函数名是执行函数体的入口地址

4.3、可通过函数类型定义函数指针:FuncType* pointer;

4.4、也可以直接定义:type(*pointer)(parameter list);

pointer为函数指针变量名

type为指向函数的返回值类型

parameter list为指向函数的参数类型列表

函数指针的本质与使用

#include <stdio.h>typedef int(FUNC)(int);int test(int i){    return i * i;}void f(){    printf("Call f()...\n");}int main(){    FUNC* pt = test;//函数名是函数的入口地址        void(*pf)() = &f;//定义了pf函数指针,f和&f是相同的,这点与数组名不同。         //从c语言的发展来讲,f是&f的简化。        pf();    (*pf)();     //这两种并没有本质的区别        printf("Function pointer call: %d\n", pt(2));}
5、回调函数

1、回调函数是利用函数指针实现的一种调用机制

2、回调机制原理:

调用者不知道具体事件发生的时候需要调用的具体函数

被调用函数不知道何时被调用,只知道被调用后需要完成的任务

当具体事件发生时,调用者通过函数指针调用具体函数

3、回调机制的将调用者和被调函数分开,两者互不依赖
回调函数实例:

#include <stdio.h>typedef int(*FUNCTION)(int);int g(int n, FUNCTION f){    int i = 0;    int ret = 0;        for(i=1; i<=n; i++)    {        ret += i*f(i);    }        return ret;}int f1(int x){    return x + 1;}int f2(int x){    return 2*x - 1;}int f3(int x){    return -x;}int main(){    printf("x * f1(x): %d\n", g(3, f1));    printf("x * f2(x): %d\n", g(3, f2));    printf("x * f3(x): %d\n", g(3, f3));}
6、指针阅读技巧解析

右左法则:

1、从最里层的圆括号中未定义的标示符看起

2、首先往右看,再往左看

3、当遇到圆括号或者方括号时可以确定部分类型,并调转方向

4、重复2、3步骤,直到阅读结束
复杂指针阅读:

#include <stdio.h>

int main()

    int (*p2)(int*, int (*f)(int*));
    
    int (*p3[5])(int*);  //p3是一个包含五个元素的数组,元素为指针,指针为函数指针,
    
    int (*(*p4)[5])(int*);  //p4是一个指针,指针为数组指针,数组有五个元素,每个元素为指针,指针为函数指针
    
    int (*(*p5)(int*))[5];
}

0 0
原创粉丝点击