C语言学习笔记(六) 指针和数组

使用C语言就必然会使用到指针和数组。看下面的代码:

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

似乎二者的用法完全相同,但其实指针和数组没有任何关系。这里先总结下指针和数组的区别,后面再详细区分下两者作为参数的情形及其用途。

指针和数组的区别

指针 数组
保存数据的地址,任何存入指针变量 p 的数据都会被当作地址来处理。p 本身的地址由编译器另外存储,存储在哪里,我们并不知道。 保存数据,数组名 a 代表的是数组首元素的首地址而不是数组的首地址。&a 才是整个数组的首地址。a 本身的地址由编译器另外存储,存储在哪里,我们并不知道。
间接访问数据,首先取得指针变量 p 的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。指针可以作为左值,也可以作为右值。 直接访问数据,数组名 a 是整个数组的名字,
数组内每个元素并没有名字。只能通过“具
名 +匿名”的方式来访问其某个元素,不能把
数组当一个整体来进行读写操作。即数组名不能作为左值。
访问的实质是先取 p 的内容然后加上i* sizeof(类型)个 byte 作为数据的真正地址。 访问的实质都是 a 所代表的数组首元素的首地址加上 i*sizeof(类型 )个 byte 作为数据的真正地址。
通常用于动态数据结构 通常用于存储固定数目且数据类型相同的元素。
相关的函数为 malloc 和 free。 隐式分配和删除
通常指向匿名数据(当然也可指向具名数据) 自身即为数组名


一维数组参数

先看下面的示例代码:

void foo(int a[10]){
    printf("%d",a[0]);
}

int main(int argc, char** argv){
    int a[2] = {1,2};
    foo(a[10]);
    return 0;
}

上面的代码至少有两个问题:1)a[10]是肯定不存在的,但编译器并不实际计算地址,因此不能发现这样的错误。2)函数参数实际上需要一个int *指针,但我们传递的是一个int类型的数据。这时候把a[10]里面的数据当成指针地址访问,肯定也会出问题。此外,函数的声明很容易让读者误认为只能传递长度为10的数组。

这里有一个规则:C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。这么做主要是从不浪费大量时间去拷贝数组的角度考虑的。基于这一点,实际传递的数组大小与函数形参指定的数组大小并没有关系。

一级指针参数

要时刻注意:函数中使用的参数变量是实参的一份拷贝。看下面的代码:

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

void allocateMemory(char *p, int size){
    p = (char *)malloc(size * sizeof(char));
}

int main(int argc, char** argv){
    char *p = NULL;
    allocateMemory(p, 30);
    strcpy(p, "Hello");        //这里会出现错误
    free(p);                   //并没有效果
    return 0;
}

上面的代码在运行到strcpy时p的值仍然为NULL,我们试图分配的空间被赋给allocate函数里的拷贝变量去了,发生了内存泄漏和空指针。而这个拷贝变量我们根本没法处理,它是编译器自动分配回收的。所以使用一级指针参数一定要注意这个问题。要继续使用一级指针并解决这个问题,可以给函数加上返回值:

char * allocateMemory(char *p, int size){
    p = (char *)malloc(size * sizeof(char));
    return p;
}

多级指针参数

还有一种办法是使用多级指针:

void allocateMemory(char **p, int size){
    *p = (char *)malloc(size * sizeof(char));
}

int main(int argc, char** argv){
    char *p = NULL;
    allocateMemory(&p, 30);
    strcpy(p, "Hello");
    free(p);
    return 0;
}

注意,这里使用了二级指针,在使用的时候参数要用&p, 而不是p。这样传递过去的是p的地址,被调函数内部拷贝一个地址并不影响p值的使用,也就达到了我们的目的。

多维数组参数

数组有一维的,也有二维以上的。由于内存是线性的,我们把高维数组理解为数组的数组。但有一条规则却不能如此递归:C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。也就是说只有一维数组才是如此,当数组超过一维时,将第一维改写为指向数组首元素首地址的指针之后,后面的维再也不可改写。比如: a[3][4][5]作为参数时可以被改写为(*p)[4][5]。在声明多维数组为参数的时候,也只能省略第一维的长度。例如下面的声明:

void funa(int a[1][3]){}    //正确
void funb(int a[][3]){}     //正确
void func(int a[][]){}      //错误

C语言中还有一个复杂的指针叫做函数指针,这将在下一篇笔记里记录。本文先到这里。

时间: 2024-10-23 17:15:04

C语言学习笔记(六) 指针和数组的相关文章

C语言学习笔记(五) 数组

数组 数组的出现就是为了解决大量同类型数据的存储和使用的问题: 数组的分类:一维数组.二维数组. 一维数组:为多个变量连续分配存储控件:所有的变量的数据类型必须相同:所有变量所占的字节大小必须相等: 例如:int a[4]; 一维数组名不代表数组中的所有元素而是代表数组中第一个元素的地址: 数组的初始化: 完全初始化: int a[4] = {1,2,3,4}; 不完全初始化: int a[4] = {1};  未被初始化的元素的值默认为0; 数组清零 int a[4] = {0}; 不初始化:

C语言学习笔记-8.指针

一.什么是指针 1.指针是存放另一个变量内存地址的变量 指针型变量的长度为4个字节,32位 2.宏定义NULL,表示0 良好的编程习惯:暂时不使用指针时,将其值设为NULL 3.指针定义后,若未被初始化,则其值为0xCCCCCCCC(Visual Studio中) 若访问此地址,则程序崩溃 4.指针作用:对主函数中实参变量值进行修改 例:void get(int *input) { scanf(“%d”, input);             //注意:不能写成&input } 二.指针与数组

《C程序设计语言》笔记 (五) 指针与数组

5.1 指针与地址 指针是一种保存变量地址的变量 ANSI C使用类型void*(指向void的指针)代替char *作为通用指针的类型 一元运算符&可用于取一个对象的地址: p = &c 把C的地址赋值给变量p,我们称p为指向c的指针.地址运算符&只能应用于内存中的对象,即变量与数组元素. 不能作用于表达式 常量或register变量 一元运算符*是间接寻址或间接引用运算符.当它作用于指针时,将访问指针所指向的对象 由于指针也是变量,所以在程序中可以直接使用,而不必通过间接引用的

《C语言学习笔记》指针数组及其应用

C语言中,最灵活但又容易出错的莫过于指针了.而指针数组,是在C中很常见的一个应用.指针数组的意思是说,这个数组存储的所有对象都为指针.除了存储对象为指针,即一个地址外,其它操作和普通数组完全一样. 1 #include <stdion.h> 2 3 int main() { 4 int i; 5 char *name[] = {"BASIC", "FORTRAN", "C++", "Pascal", "C

Go语言学习笔记(三)数组 &amp; 切片 &amp; map

加 Golang学习 QQ群共同学习进步成家立业工作 ^-^ 群号:96933959 数组 Arrays 数组是同一种数据类型的固定长度的序列. 数组是值类型,因此改变副本的值,不会改变本身的值: 当作为方法的入参传入时将复制一份数组而不是引用同一指针. 通过从0开始的下标索引访问元素值. 数组定义 var a []int a = make([]int, 5) var a1 [5]int = [5]int{1, 2, 3, 4, 5} //len:5 content:[1 2 3 4 5] va

梓益C语言学习笔记之指针

一.32位平台下,地址是32位,所以指针变量占32位,共4个字节 二.内存单元的地址即为指针,存放指针的变量称为指针变量,故:"指针"是指地址,是常量,"指针变量"是取值为地址的变量. char*型指针一次取一个字节,int*型指针一次取4个字节,double*取两次4字节 数组名是指针,代表数组首元素的地址,但数组名是常量,不能修改. 三.通过指针变量可以取得数组或函数的首地址 int*p,表示p是一个指针变量,它的值是某个整型变量的地址 指针数组:int *a[

初探swift语言的学习笔记六(ARC-自动引用计数,内存管理)

Swift使用自动引用计数(ARC)来管理应用程序的内存使用.这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理.当实例并不再被需要时,ARC会自动释放这些实例所使用的内存. 另外需要注意的: 引用计数仅仅作用于类实例上.结构和枚举是值类型,而非引用类型,所以不能被引用存储和传递. swift的ARC工作过程 每当创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息. 另外当实例不再被使用时,ARC会释放实例所占用的内存,这些

R语言学习笔记

參考:W.N. Venables, D.M. Smith and the R DCT: Introduction to R -- Notes on R: A Programming Environment for Data Analysis and Graphics,2003. http://bayes.math.montana.edu/Rweb/Rnotes/R.html 前言:关于R 在R的官方教程里是这么给R下注解的:一个数据分析和图形显示的程序设计环境(A system for data

学习笔记:指针之C

因为某些原因来好好的将C重新梳理一遍,过去一年也是个渣,好在还能重新来过.将C和指针中指针一节学习了三四次,再将后面的习题做了一做.现在再好好回顾一下.至于对错就不过多评价. 指针的确是C的特点之一,从最初接这玩意就没把它弄的很明白.据说把它真正吃透的人也不多.这也只是我的学习笔记.指针,记得当初C语言老师说的一点,就是地址,其他就没啥印象了.自从自己做题就是记住了这点,指针就是地址,访问相应内存空间的值是间接访问,要用' * ',也叫解引用指针. 通过C和指针这本参考书,我才知道我们是用变量来