《C专家编程》数组和指针并不同--多维数组

《C专家编程》数组和指针并不同

标签(空格分隔): 程序设计论著笔记


1. 背景理解

1.1 区分定义与声明 p83

  • 声明相当于普通声明:它所说明的并非自身,而是描述其他地方创建的对象,声明可以多次出现;
  • 定义相当于特殊声明:它可以为对象分配内存,只能出现在一个地方。

1.2 数组和指针的访问方式

  • 左值和右值

    ???????? X = Y ;

    • 符号X的含义是X所代表的地址,这被称为左值,左值在编译时可知,左值表示存储结果的地方。
    • 符号Y的含义是Y所代表的地址的内容,这被称为右值,右值直到运行时可知,如无特别说明,右值表示“Y的内容”。
    • :数组名可以用于确定对象在内存中的位置,也是左值,但它不能作为赋值对象,因此数组名是个左值但不是可修改的左值。
  • 数组和指针如何被访问
    • 数组:char a[9]="abcdefgh";... c=a[i];

      编译器符号表具有一个地址,9980,运行步骤1:取i的值,将它与9980相加,运行步骤2,去地址(9980+i)的内容。

    • 指针:char* p ... c=*p;,其高速编译器p是一个指针(在许多现代的机器里它是四个字节对象),它指向的对象是一个字节。为了取得这个字符,必须得到地址p的内容,把它作为字符的地址并从这个地址中取得这个字符。指针的访问要灵活的多,但要增加一次额外提取:

      编译器有一个符号p,它的地址为4624,运行时步骤1,取地址4624的内容,就是‘5081’;运行步骤2:取地址5081的内容。

2. 数组与指针的区别

2.1 不同点:

序号 指针 数组
1 保存数据的地址 保存数据
2 间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。如果指针有一个下标[i],就把指针的内容加i作为地址,从中提取数据 直接访问数据,a[i]只是简单的以a+i为地址取得数据
3 通常用于动态数据结构 通常用于存储固定数目且数据类型相同的元素
4 先关函数为malloc(),free() 隐士分配和删除
5 通常指向匿名数据 自身即为数据明

注!注!注!

数组和指针都可以在他们的定义中使用字符串常量进行初始化,尽管看上去一样,但底层机制不一样。

定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义的同时赋给指针一个字符串常量进行初始化。

char *p="breadfruit";

注意只有对字符串常量才是如此。不能指望浮点数之类的常量分配空间,如:

float *pip=3.14;/*错误,无法编译通过*/

在ANSI C中,初始化指针所创建的字符串常量被定义为只读(存储在静态区)。如果试图通过指针修改这个字符串的值,程序出现未定义(出错)

数组也可以用字符串进常量进行初始化:

char a[]="gooseberry";

与指针相反,由字符串常量初始化的数组是可以修改的(存储在栈中)。其中单个字符在以后可以改变:

strncpy(a,"black",5);

2.2 相同点:

  • tips:所有作为函数参数的数组名总是可以通过编译器转换为指针,且被当做指向该数组第一个元素的指针

    编译器只向函数传递数组的地址,而不是整个数组的拷贝,其实处于效率考虑。

    所以,一下形式均合法,且最终被编译器转化为指针形似:

    func(int* a);
    func(int a[]);
    func(int a[10]);
    

    所以,在函数内部,使用sizeof(a)无法得到数组的大小,因为数组a[]在作 为形参时被自动转化为指针,所以sizeof(a)一般为4(存储指针的空间)

  • a[i]这样的形式对数组进行访问总是被编译器“改写”成或解释为像*(a+i)这样的指针访问。

3. C语言的多维数组

C语言中,定义和引用多维数组唯一的方法是使用数组的数组:

——————————————————————

char carrot[10][20];//声明一个10*20的多维数组

或者声明可以看上去更像“数组的数组”形式

typedef char vegetable[20];

vagetable carrot[10];

不论哪种情况,访问单个字节都可以通过carrot[i][j]的形式,

编译器在编译时会把它解析为*(*(carrot+i)+j)的形式

——————————————————————

tips:C语言的数组就是一维数组:

??当提到C语言中的数组时,就把它看作是一种向量(vector),也就是某种对象的以为数组,数组的元素可以是另一个数组。

### 3.1 内存中数组的布局

在C语言多为数组中,最右边的下标是最先变化的,这个约定被称为“行主序”.其实线性存储, a[i][j] 与 *(*(a+i)+j)等价

### 3.2 多维数组初始化:

int a[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };

3.2 多维数组的声明

指针数组

char *pea[4]; //一维指针数组,每个指针指向一个字符串,数组的每个元素内为一个char*指针

(注意区别:char(*pea)[4];数组指针,一个指向具有4个字符类型元素的数组

可以进行如下初始化:

for (j=0;j<=4;j++)

pea[j]=malloc(6);

也可以一次性的用malloc分配整个数组:

malloc(row_size*column_size*sizeof(char));

软件信条

对于s[i][j]这样形式的原型声明:

int s[2][3];/* int 型 二维 数组*/
int *s[2]; /* int 指针数组,每个指针指向一个含有3个元素的以为一维数组*/
int **s; /* 指向指针的指针*/
int (*s)[3];/* 数组指针,一个指向为int数组(长度为3)的指针

都是由于作为左值的数组名本编译器当做指针。

锯齿状数组:

如果声明一个字符串指针数组,并根据需要为这些字符串分配内存,将会大大节省系统资源:

char* turnip[UMPTEEN]
char my_string[]="your message here";
/*贡献字符串*/
turnip[i]=&my_string[0];
/*拷贝字符串*/
turnip[j]=malloc(strlen(my_string)+1);
strcpy(turnip[j],my_string);

tips: p225

数组和指针参数被编译器的修改规则:

实参 所匹配的形参
数组的数组 char c[2][3] char(*)[3] 数组指针
指针数组 char *c[2] char** 指针的指针
数组指针(行指针) char (*a)[3] char (*a)[3] 不改变
指针的指针 char **a char**a 不改变

3.3 使用指针向函数传递一个多维数组

  1. func(int array[10][20]);

    这种办法最简单,但作用最小,其迫使你只处理10行20列的int型数组

  2. 省略第一维长度:

    func(int array[][20]);

    但其依然限定每行必须是20个整数的长度

    类似的也可以声明为:

    func(int (*array)[20]);传递一个数组指针,数组的长度为20.

  3. func(int ** array);

    动态数组形式:二维数组在堆上分配,各行地址空间不一定连续,函数参数使用指针形式:

    为了进一步提高泛用性,把二维数组空间的分配也动态化,使用malloc()在堆上分配空间,重复一下前言中的方式如下:

//1. 数组声明
int **array;
array = (int **)malloc(m *sizeof(int *));
for(i=0;i<M;i++)
    array[i] = (int *)malloc(n *sizeof(int));
这时,在分配空间的作用域里,对0<=i<M,0<=j<N,array[i][j]的访问完全没有问题。那么,对应地,函数写作
//2. 函数声明

int func(int **array,int m,int n) {
    ...
    printf("%d ", *(*(array+i)+j));
    ...
}

值得注意的是,虽然malloc()每次分配的空间在地址上是连续的,但是多次malloc()分配的空间之间并不一定是连续的,这与在栈上分配的二维矩阵有着根本的不同,对于二维数组array[3][3],不能再用array[1][4]来访问array[2][1]了,前者地址越界。
  1. 折中形式:用堆上分配的一维数组表示二维数组,函数参数使用指针形式

    用一维数组来实现二维数组,是一种折中方案,但是很好理解,也不易出错。这样分配的数组空间是连续的。使用时需要把两维下标转化为一维下标。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int func(int *array, int m, int n) {
    int i,j;
    for(i=0;i<m;i++) {
        for(j=0;j<n;j++)
            printf("\t%d",*(array+i*n+j));
        printf("\n");
    }
    return 0;
}

int main(int argc,char** argv) {
    int m,n,i;
    int *array;
    assert(argc == 3);
    m = atoi(argv[1]);
    n = atoi(argv[2]);
    array = (int*)malloc(m*n*sizeof(int));
    for(i=0;i<m*n;i++)
        array[i] = i;
    func(array,m,n);
    return 0;
}
  1. 较新的编译器:用栈上分配的直到执行时才确定大小的二维数组(即C99的变长数组,它允许使用变量定义数组各维)
int quarters = 4;
int regions = 5;
double sales[quarters][regions]; //一个变长数组VAL

变长数组有一些限制:变长数组必须是自动存储类的,意味着它们必须在函数内部或作为函数参数声明,而且声明时不可以进行初始化。

C90不支持这种形式,C99支持,因此一些较新的编译器可以对下面的代码进行执行。注意print()的参数顺序不能改变。

void print(int x, int y, int a[x][y]){
    printf("\n");
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++)
            printf("%d     ", a[i][j]);
        printf("\n");
    }
}

// Function to initialize the two-dimensional array
void init_2d(int *a, int x, int y){
    int i, j;
    for(i = 0; i < x; i++){
        for(j = 0; j < y; j++){
            a[i*y + j] = i + j;
        }
        printf("\n");
    }
}

int main(){
    int m , n ;
    scanf("%d %d",&m,&n);
    int a[m][n];  // a two dimensional whose size has been defined using variables
    init_2d(a, m, n);
    print(m, n, a);
}

 这段代码出自http://stackoverflow.com/questions/17181577/two-dimensional-arrays-in-c

  (2013.7.28更新)

  另外,这种分配方式仍然是在栈上,相关讨论可见于http://bbs.csdn.net/topics/90350681

spf 20160121 二维数组使用测试

  1. 动态二维数组,在堆上分配,但存在内存不连续情况

void PrintArray(int **array, int m, int n)
{
    if (array == NULL || (*array) == NULL)
        return;
    printf("********打印二维数组**************\n");

    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
            printf("%d   ", array[i][j]);
        printf("\n");
    }

}
int main()
{
    int M, N;

    printf("请输入二维数组的行和列,以空格隔开:\n");

    scanf("%d %d", &M, &N);

    int **array=NULL;//声明array[m][n] 二维数组
    array = (int**)malloc(M*sizeof(int*));
    if (array == NULL)
        return -1;
    for (int i = 0; i < M; i++)
    {
        array[i] = (int*)malloc(N*sizeof(int));
        if (array[i] == NULL)
            return -1;
    }

    printf("请输入%d行%d列的二维数组\n",M,N);
    for (int i = 0; i < M; i++)
        for (int j = 0; j < N; j++)
            scanf("%d", &array[i][j]);
    PrintArray(array, M, N);

    for (int i = 0; i < M; i++)
    {
        free(array[i]);
        array[i] = NULL;
    }

    free(array);
    array = NULL;

    free(NULL);
    system("pause");
    return 0;
}
  1. 动态一维数组,当做二维数组使用(推荐)
void Print(int* array, int m, int n)
{
    if (array == NULL)
        return;
    printf("********打印二维数组**************\n");

    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
            printf("%d   ", *(array+i*n+j));
        printf("\n");
    }
}
int main()
{
    int M, N;

    printf("请输入二维数组的行和列,以空格隔开:\n");

    scanf("%d %d", &M, &N);
    int* array = (int*)malloc(M*N*sizeof(int));
    if (array == NULL)
        return -1;

    printf("请输入%d行%d列的二维数组\n", M, N);
    for (int i = 0; i < M; i++)
        for (int j = 0; j < N; j++)
            scanf("%d",array+i*N+j);
    Print(array, M, N);

    free(array);
    array = NULL;

    system("pause");
    return 0;
}

4. 参考文献:

  1. http://www.cnblogs.com/wuyuegb2312/archive/2013/06/14/3135277.html
  2. http://www.cnblogs.com/cpoint/p/3368380.html
时间: 2024-10-27 05:43:53

《C专家编程》数组和指针并不同--多维数组的相关文章

编程题:指针变量指向结构体数组。

编程题:指针变量指向结构体数组. #include<stdio.h> void main() { struct person {char name[20]; char sex; int age; float height; }per[3]={{"Li Ping",'M',20,175},{"Wang Ling",'F',19,162.5}, {"Zhao Hui",'M',20,178}}; struct person *p; for

c语言中如何通过二级指针来操作二维数组

通过二级指针去访问二维数组需要先给二级指针分配等同于二维数组行数的一维数组指针,然后把二维数组的每行首地址赋值给对应位置的一维指针上.之后就可以通过二维指针直接访问了. 参考代码如下,可以看具体注释辅助理解. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h>//输入输出头文件. #include <stdlib.h>//本程序需要用到malloc/free函数,引

将树形结构的数组按照顺序遍历为二维数组

/** * 将树形结构的数组按照顺序遍历为二维数组 * renxing,2015年8月7日 11:06:47 */ function arr_child ($array) { static $res; if (!is_array($array)) { return false; } foreach ($array as $k=>$v) { if (is_array($v) && isset($v['child'])) { $child = $v['child']; //将这个数组的子

稀疏数组的经典应用(二维数组与稀疏数组的相互转换)

package com.zhangwl.数据结构.稀疏数组; /** * @ClassName SparseArray * @Description 棋盘游戏演示 * @Author zhangwl * @Date 2019/10/10 22:54 * @Version 1.0 **/public class SparseArray { public static void main(String[] args) { /*创建一个原始的二维数组11 * 11 ,0:表示没有棋子 :1:表示黑子:

使用指针形式向函数传递多维数组并在函数内部使用指针形式对多维数组进行引用和修改

定义一个double型二维数组A和另一个同等尺寸的double空二维数组B,编写一个函数将A中的数据复制到B中.并测试该程序. 1 #include <stdio.h> 2 void copy_ptr(const double (*s)[12],double (*tar)[12],int row,int col); 3 int main(void){ 4 const double rain[5][12]={ 5 {4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,

C++入门经典-例6.11-使用指针变量遍历二维数组

1:代码如下: // 6.11.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #include <iomanip> using namespace std; void main() { int a[4][3]={1,2,3,4,5,6,7,8,9,10,11,12}; int *p; p=a[0]; for(int i=0;i<sizeof(a)/sizeof(int);i+

用指针实现对二维数组元素的访问

在C语言中,多维数组实际上是数组的数组,二维数组就是每个元素都是一个一维数组的一维数组. 例如a[3][4]: 定义了一个3行4列的二维数组,可以将a看成是有a[0].a[1].a[2]三个元素组成的一维数组,而每个元素a[i]都是一个有4个整形元素的数组. 1 #include<stdio.h> 2 3 int main() 4 { 5 int i, j, a[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}}, *p; 6 p = &a[0][

剑指Offer编程题(Java实现)——二维数组中的查找

题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 解题思路 根据排序特点,该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边. 因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来缩小查找区间,当前元素的查找区间为左下角的所有元素. 时间复杂度 O(M + N),空间复杂度 O(1).其中 M 为行数,N

VB.NET 数组的定义 动态使用 多维数组

我们都知道在全部程序设计语言中数组都是一个非常重要的概念,数组的作用是同意程序猿用同一个名称来引用多个变量,因此採用数组索引来区分这些变量.非常多情况下利用数组索引来设置一个循环,这样就能够高效地处理复杂的情况,因此在非常多情况下,使用数组能够缩短或者简化程序的代码.本文主要介绍VB.NET数组的使用,希望对大家的使用带来帮助. 数组中的第一个元素的下标称为下界,最后一个元素的下标称为上界,其余的元素连续地分布在上下界之间,而且数组在内存中也是用连续的区域来存储的,所以要求声明数组每维的长度不能