《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

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

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

    通过二级指针去访问二维数组需要先给二级指针分配等同于二维数组行数的一维数组指针,然后把二维数组的每行首地址赋值给对应位置的一维指针上.之后就可以通过二维指针直接访问了. 参考代码如下,可以看具体注释辅 ...

  2. 用js来实现那些数据结构03(数组篇03-排序及多维数组)

    终于,这是有关于数组的最后一篇,下一篇会真真切切给大家带来数据结构在js中的实现方式.那么这篇文章还是得啰嗦一下数组的相关知识,因为数组真的太重要了!不要怀疑数组在JS中的重要性与实用性.这篇文章分为 ...

  3. java 数组基础学习(一维二维数组)

    1.一维数组 1>静态初始化:数据类型[ ] 变量名 = {元素} 例:int[ ] arr = {1,2} 动态初始化:数据类型[ ] 变量名 = new数据类型[数据长度] 例:int[ ] ...

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

    1:代码如下: // 6.11.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <iostream> #inc ...

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

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  6. 【剑指Offer面试编程题】题目1384:二维数组中的查找--九度OJ

    题目描述: 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 输入: 输入可能包含 ...

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

    我们都知道在全部程序设计语言中数组都是一个非常重要的概念,数组的作用是同意程序猿用同一个名称来引用多个变量,因此採用数组索引来区分这些变量.非常多情况下利用数组索引来设置一个循环,这样就能够高效地处理 ...

  8. C语言数组篇(四)二维数组

      二维数组声明: ][] ={{,,},{,,}; //两行 三列         二维数组在声明的时候可以不写行,但一定要写列 ] = {{,},{,,},{}}; //未声明的地方自动补零 二维 ...

  9. 数组(Array),二维数组,三维数组

    数组(Array):相同类型数据的集合就叫做数组. (一)定义数组的方法: A) type[] 变量名 = new type[数组中元素的个数] 例如: int[] a = new int[10] ; ...

随机推荐

  1. javaScript 笔记(5) --- jQuery(上)

    这节整理整理 iquery.js 相关的内容... 目录 --- jQuery 语法 --- 文档就绪事件 --- jQuery 选择器 --- jQuery 事件 --- jQuery 效果 jQu ...

  2. HH去散步(bzoj 1875)

    Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回. 又因 ...

  3. [CODEVS1051]接龙游戏

    题目描述 给出了N个单词,已经按长度排好了序.如果某单词i是某单词j的前缀,i->j算一次接龙(两个相同的单词不能算接龙). 你的任务是:对于输入的单词,找出最长的龙. 输入描述 Input D ...

  4. Vim查找替换及正则表达式的使用

    原文地址:http://tanqisen.github.io/blog/2013/01/13/vim-search-replace-regex/ 简单替换表达式 :[range]s/from/to/[ ...

  5. C#将图片进行马赛克处理

    /// <summary> /// 马赛克处理 /// </summary> /// <param name="bitmap"></par ...

  6. 洛谷——P1227 [JSOI2008]完美的对称

    P1227 [JSOI2008]完美的对称 题目描述 在峰会期间,必须使用许多保镖保卫参加会议的各国代表.代表们除了由他自己的随身保镖保护外,组委会还指派了一些其他的特工和阻击手保护他们.为了使他们的 ...

  7. Fennec VS. Snuke --AtCoder

    题目描述 Fennec and Snuke are playing a board game.On the board, there are N cells numbered 1 through N, ...

  8. Difference between a Hard Link and Soft (Symbolic) Link

    Within the Unix/Linux file system, linking lets you create file shortcuts to link one or more files. ...

  9. IDEA使用Maven打包时如何去掉测试阶段

    如图

  10. 126. Word Ladder II(hard)

    126. Word Ladder II 题目 Given two words (beginWord and endWord), and a dictionary's word list, find a ...