第十一章 指针和数组

一旦给出数组的定义,编译系统就会为其在内存中分配固定的存储单元,相应的,数组的首地址也就确定了

C语言中的数组名有特殊的含义,它代表存放数组元素的连续存储空间的首地址

//L11-1

#include <stdio.h>
int main()
{
int a[5], i;
printf("Input five numbers:");
for (i=0; i<5; i++)
{
scanf("%d", &a[i]); /* 用下标法引用数组元素 */
}
for (i=0; i<5; i++)
{
printf("%4d", a[i]); /* 用下标法引用数组元素 */
}
printf("\n");
return 0;
}
//运行结果
Input five numbers:1 2 3 4 5
1 2 3 4 5

表达式a+i代表数组中下标为i的元素a[i]的地址(&a[i])

因此可以通过间接寻址来引用数组中的元素

*(a+i)表示取出首地址后第i个元素的内容,即a[i]

指针的算术运算和关系运算常常是针对数组元素而言的

如果定义了一个指向整型数据的指针变量p,并使其值为数组a的首地址

那么可以通过这个指针变量p来访问数组a中的元素

p+1和p++是不同的操作

p+1并不改变当前指针的指向

p++相当于将指针变量向前移动一个元素位置

p++是为指针变量加上1*sizeof(基类型)个字节

用数组名和用指向一维数组的指针变量作函数实参,向被调函数传递的都是数组的起始地址,都是模拟按引用调用

对一维数组而言,用数组作函数形参和用指针变量作函数形参本质相同,因为他们接收的都是数组的起始地址,都需按此地址对主调函数中的实参数组进行间接寻址

//L11-2

#include <stdio.h>
void InputArray(int a[], int n);
void OutputArray(int a[], int n);
int main()
{
int a[5];
int *p = a;
printf("Input five numbers:");
InputArray(p, 5); /* 用指向一维数组的指针变量作为函数实参 */
OutputArray(p, 5); /* 用指向一维数组的指针变量作为函数实参 */
return 0;
} void InputArray(int a[], int n) /* 形参声明为数组,输入数组元素值 */
{
int i;
for (i=0; i<n; i++)
{
scanf("%d", &a[i]); /* 用下标法访问数组元素 */
}
}
void OutputArray(int a[], int n) /* 形参声明为数组,输出数组元素值 */
{
int i;
for (i=0; i<n; i++)
{
printf("%4d", a[i]); /* 用下标法访问数组元素 */
}
printf("\n");
}

指针与二维数组的关系:

int a[3][4];

a[i]可以看成是a[i][0]、a[i][1]、a[i][2]、a[i][3]这4个元素组成的一维整型数组的数组名,代表这个一维数组的第1个元素a[i][0]的地址(&a[i][0])

a[i]+j即*(a+i)+j代表这个数组中下标为j的元素的地址(&a[i][j])

*(a[i]+j)即*(*(a+i)+j)代表a[i][j]

二维数组的行指针和列指针:

int a[3][4];

行指针

int (*p)[4];

定义了一个可以指向含有4个元素的一维整型数组的指针变量,也可作为一个指向二维数组的指针变量,它所指向的二维数组的每一行有4个元素

初始化

p = a;

p = &a[0];

引用

p[i][j];

*(p[i]+j);

*(*(p+i)+j);

(*(p+i))[j];

列指针

int *p;

初始化

p = a[0];

p = *a;

p = &a[0][0];

引用

p+i*n+j代表&a[i][j]

p[i*n+j]或*(p+i*n+j)代表a[i][j]

//L11-3

#include <stdio.h>
void InputArray(int *p, int m, int n);
void OutputArray(int *p, int m, int n);
int main()
{
int a[3][4];
printf("Input 3*4 numbers:\n");
InputArray(*a, 3, 4); /* 向函数传递二维数组的第0行第0列的地址 */
OutputArray(*a, 3, 4); /* 向函数传递二维数组的第0行第0列的地址 */
return 0;
}
/* 形参声明为指向二维数组的列指针,输入数组元素值 */
void InputArray(int *p, int m, int n)
{
int i, j;
for(i = 0; i<m; i++)
{
for(j = 0; j<n; j++)
{
scanf("%d", &p[i*n+j]);
}
}
}
/* 形参声明为指向二维数组的列指针,输出数组元素值 */
void OutputArray(int *p, int m, int n)
{
int i, j;
for(i = 0; i<m; i++)
{
for(j = 0; j<n; j++)
{
printf("%4d", p[i*n+j]);
}
printf("\n");
}
}
//运行结果
Input 3*4 numbers:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4
5 6 7 8
9 10 11 12

指针数组:

由若干基类型相同的指针构成的数组,称为指针数组

指针数组中的每个元素都是一个指针,且这些指针指向相同数据类型的变量

因指针数组中的元素是一个指针,所以在使用指针数组之前必须对指针数组元素进行初始化

//L11-4

#include  <stdio.h>
#include <string.h>
#define MAX_LEN 10 /* 字符串最大长度 */
#define N 150 /* 字符串个数 */
void SortString(char *ptr[], int n);
int main()
{
int i, n;
char name[N][MAX_LEN]; /* 定义二维字符数组 */
char *pStr[N]; /* 定义字符指针数组 */
printf("How many countries?");
scanf("%d", &n);
getchar(); /* 读走输入缓冲区中的回车符 */
printf("Input their names:\n");
for (i=0; i<n; i++)
{
pStr[i] = name[i]; /* 让pStr[i]指向二维字符数组name的第i行 */
gets(pStr[i]); /* 输入第i个字符串到pStr[i]指向的内存 */
}
SortString(pStr, n); /* 字符串按字典顺序排序 */
printf("Sorted results:\n");
for (i=0; i<n; i++)
{
puts(pStr[i]); /* 输出排序后的n个字符串 */
}
return 0;
}
/*函数功能:用指针数组作函数参数,采用交换法实现字符串按字典顺序排序 */
void SortString(char *ptr[], int n)
{
int i, j;
char *temp = NULL; /* 因交换的是字符串的地址值,故temp定义为指针变量 */
for (i=0; i<n-1; i++)
{
for (j = i+1; j<n; j++)
{
if (strcmp(ptr[j], ptr[i]) < 0) /* 交换指向字符串的指针 */
{
temp = ptr[i];
ptr[i] = ptr[j];
ptr[j] = temp;
}
}
}
}

上述程序中,保存在二维字符数组name中的字符串并没有发生任何变化,即执行SortString()后字符串在其实际物理存储空间中的存放位置并没有改变,但每个指针数组元素所指向的字符串在排序前后发生了变化,即这种排序仅改变了指针数组中各元素的指向

物理排序:通过移动字符串在实际物理存储空间中的存放位置而实现的排序

索引排序:通过移动字符串的索引地址(指针的指向)而实现的排序

相对于使用二维数组实现物理排序的方法而言,使用指针数组实现索引排序的程序执行效率更高一些

指针数组用于表示命令行参数

//L11-5

#include  <stdio.h>//该程序的文件名是echo.c,程序编译连接后的可执行文件名为echo.exe
int main(int argc, char *argv[])//带参数的main()函数的定义
//第1个形参argc被声明为整型变量,用于存放命令行中参数的个数
//第2个形参argv被声明为指针数组,用于接收命令行参数
//由于所有的命令行参数都被当做字符串来处理,所以在这里字符指针数组argv的各元素依次指向命令行中的参数
{
int i;
printf("The number of command line arguments is:%d\n", argc);
printf("The program name is:%s\n", argv[0]);
if (argc > 1)
{
printf("The other arguments are following:\n");
for (i=1; i<argc; i++)
{
printf("%s\n", argv[i]);
}
}
return 0;
}
//运行结果
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。 PS C:\Users\lenovo> Desktop\echo.exe programming is fun
The number of command line arguments is:4
The program name is:C:\Users\lenovo\Desktop\echo.exe
The other arguments are following:
programming
is
fun

命令行参数很有用,尤其是在批处理命令中使用较为广泛

例如,可通过命令行参数向一个程序传递这个程序所要处理的文件的名字,还可以用来指定命令的选项等

当程序中不需要命令行参数时,一般就在main后面的括号里使用关键字void将函数main()声明为无参数

C程序的内存映像:

  • 只读存储区:存放程序的机器代码和字符串常量等只读数据
  • 静态存储区:存放程序中的全局变量和静态变量等
  • 堆:保存函数调用时的返回地址、函数的形参、局部变量及CPU的当前状态等程序的运行信息
  • 栈:自由存储区,程序可利用C的动态内存分配函数来使用它

C语言程序中变量的内存分配方式:

  • 从静态存储区分配
  • 在栈上分配
  • 从堆上分配

指针之所以重要:

  • 指针为函数提供修改变量值的手段
  • 指针为C的动态内存分配系统提供支持
  • 指针为动态数据结构提供支持
  • 指针可以改善某些子程序的效率

使用指针定义动态数组是指针除作为函数形参之外的另一个重要应用

动态内存分配是指在程序运行时为变量分配内存的一种方法,适用于在程序运行中需要数量可变的内存空间,即在运行时才能确定要使用多少个字节的内存来存放数据的情况

动态内存分配函数(使用时需要在程序开头将头文件<stdlib.h>包含到源程序中):

malloc()

用于分配若干字节的内存空间,返回一个指向该内存首地址的指针

若系统不能提供足够的内存单元,函数将返回空指针NULL

void *malloc(unsigned int size);

void *指针为通用指针或无类型的指针,即声明了一个指针变量,但未指定它可以指向哪一种基类型的数据

因此若要将函数调用的返回值赋予某个指针,应先根据该指针的基类型,用强转的方式将返回的指针值强转为所需的类型

calloc()

用于给若干同一类型的数据项分配连续的存储空间并赋值为0,返回首地址

void *calloc(unsigned int num, unsigned int size);

free()

释放向系统动态申请的由指针p指向的存储空间,无返回值

void free(void *p);

唯一的形参p给出的地址只能是由malloc()何calloc()申请内存时返回的地址

realloc()

用于改变原来分配的存储空间的大小

void *realloc(void *p, unsigned int size);

由于由动态内存分配得到的存储单元是无名的,只能通过指针变量来引用它

所以一旦改变了指针的指向,原来分配的内存及数据也就随之丢失了

因此不要轻易改变该指针变量的值

//L11-6

#include  <stdio.h>
#include <stdlib.h>
void InputArray(int *p, int n);
double Average(int *p, int n);
int main()
{
int *p = NULL, n;
double aver;
printf("How many students?");
scanf("%d", &n); /* 输入学生人数 */
p = (int *) malloc(n * sizeof(int)); /* 向系统申请内存 */
if (p == NULL) /* 确保指针使用前是非空指针,当p为空指针时结束程序运行 */
{
printf("No enough memory!\n");
exit(1);
}
printf("Input %d score:", n);
InputArray(p, n); /* 输入学生成绩 */
aver = Average(p, n); /* 计算平均分 */
printf("aver = %.1f\n", aver); /* 输出平均分 */
free(p); /* 释放向系统申请的内存 */
return 0;
}
/* 形参声明为指针变量,输入数组元素值 */
void InputArray(int *p, int n)
{
int i;
for (i=0; i<n; i++)
{
scanf("%d", &p[i]);
}
}
/* 形参声明为指针变量,计算数组元素的平均值 */
double Average(int *p, int n)
{
int i, sum = 0;
for (i=0; i<n; i++)
{
sum = sum + p[i];
}
return (double)sum / n;
}

//L11-7

#include  <stdio.h>
#include <stdlib.h>
void InputArray(int *p, int m, int n);
double Average(int *p, int m, int n);
int main()
{
int *p = NULL, m, n;
double aver;
printf("How many classes?");
scanf("%d", &m); /* 输入班级数 */
printf("How many students in a class?");
scanf("%d", &n); /* 输入每班学生人数 */
p = (int *)calloc(m*n, sizeof(int)); /* 向系统申请内存 */
if (p == NULL) /* 确保指针使用前是非空指针,当p为空指针时结束程序运行 */
{
printf("No enough memory!\n");
exit(1);
}
InputArray(p, m, n); /* 输入学生成绩 */
aver = Average(p, m, n); /* 计算平均分 */
printf("aver = %.1f\n", aver); /* 输出平均分 */
free(p); /* 释放向系统申请的内存 */
return 0;
}
/* 形参声明为指向二维数组的列指针,输入数组元素值 */
void InputArray(int *p, int m, int n)
{
int i, j;
for(i = 0; i<m; i++) /* m个班 */
{
printf("Please enter scores of class %d:\n", i+1);
for(j = 0; j<n; j++) /* 每班n个学生 */
{
scanf("%d", &p[i*n+j]);
}
}
}
/* 形参声明为指针变量,计算数组元素的平均值 */
double Average(int *p, int m, int n)
{
int i, j, sum = 0;
for(i = 0; i<m; i++) /* m个班 */
{
for(j = 0; j<n; j++) /* 每班n个学生 */
{
sum = sum + p[i*n+j];
}
}
return (double)sum / (m*n);
}

常见的内存错误及其对策:

  • 非法内存访问
  • 持续的内存泄露导致系统内存不足
  • 内存分配未成功就使用
  • 内存分配成功了,但是未初始化就使用
  • 内存分配成功了,也初始化了,但是发生了越界使用
  • 忘记了释放内存,造成了内存泄露
  • 释放内存后仍然继续使用

野指针:

  • 指针操作超越了变量的作用范围
  • 指针变量未被初始化
  • 指针变量所指向的动态内存被free后未置为NULL

解决对策:

  • 不要把局部变量的地址作为函数的返回值返回
  • 在定义指针变量的同时对其进行初始化
  • 尽量把malloc()集中在函数的入口处,free()集中在函数的出口处

缓冲区溢出攻击:

使用strncpy()、strncat()等“n族”字符串处理函数,通过增加一个参数来限制字符串处理的最大长度,可防止发生缓冲区溢出

只要在使用从函数外部传入的参数之前对其进行检查

  • 小心地防止指针越界
  • 防止代码访问不该访问的内存

那么抵抗缓冲区溢出攻击并非难事

C语言程序设计(十一) 指针和数组的更多相关文章

  1. 深入理解C语言中的指针与数组之指针篇

    转载于http://blog.csdn.net/hinyunsin/article/details/6662851     前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...

  2. 深入理解C语言中的指针与数组之指针篇(转载)

    前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本所在.相信,任意一家公司如果想要考察一个人对C语言的理解,指针和数组绝对是必考的一部分. 但是之前一方面之前一直在忙各种事情 ...

  3. 《C语言程序设计》指针篇<一>

    指针 指针是C语言的精华,同时也是其中的难点和重点,我在近日对这一部分内容进行了重新的研读,把其中的一些例子自己重新编写和理解了一遍.此篇博客的内容即是我自己对此书例子的一些理解和总结. 一.大问题: ...

  4. C语言中的指针和数组

    下面的内容节选自由我所执笔的会议记录.对于本文的不足之处,各位可以提出自己的看法. Q1:指针和数组到底是怎么一回事? A:指针和数组有本质的不同.指针就是一个内存地址,在32位系统下,一个指针永远占 ...

  5. C语言学习笔记--指针和数组的关系

    1.数组的本质 (1)数组是一段连续的内存空间 (2)数组的空间大小:sizeof(array_type)*array_size; (3)数组名可看做指向数组第一个元素的常量指针 (4)数组声明时编译 ...

  6. C语言-再论指针与数组

    指针与数组的天生姻缘1.以指针方式来访问数组元素(1).数组元素使用时不能整体访问,只能是单个访问.访问形式有两种:数组形式和指针形式.(2).数组形式访问数组元素:数组名[下标]:(下标从0开始(3 ...

  7. c语言学习笔记 - 指针和数组

    结合内存存储数据的机制,c语言里指针的出现和使用也就不奇怪了,如果先学了内存的一些知识,以及程序运行机制,到了c指针这块就会清晰很多. #include <stdio.h> int mai ...

  8. C语言中的指针与数组的定义与使用

    指针的特点 他就是内存中的一个地址 指针本身运算 指针所指向的内容是可以操作的 操作系统是如何管理内存的 栈空间 4M~8m的大小 当进入函数的时候会进行压栈数据 堆空间 4g的大小 1g是操作系统 ...

  9. C语言:通过指针对数组元素进行排序

    // //  main.c //  Pointer_array // //  Created by ma c on 15/8/2. //  Copyright (c) 2015年 bjsxt. All ...

  10. C语言程序设计现代方法_数组(第八章)

    C语言不要求检查下标的范围.当下标超出范围时,程序可能会执行不可预知的行为. 看下这个程序: #include <stdio.h> #define N 10 // int main() { ...

随机推荐

  1. 前端-jQuery-长期维护

     ###############    jQuery简介     ################ jQuery这是非常重要的,在django项目中大量的前端都是使用jQuery进行操作 为什么要学习 ...

  2. 牛客-小y的盒子

    题目传送门 -------------------稍加观察就会发现,4n - 1就是题目要的答案.至于为什么,看官方的题解.不过这个n非常的大,用正常快速幂解决不了.这道题我学到的就是解决幂非常大的情 ...

  3. 阿里云系统安装部署Freeswitch

    1.安装vim apt-get install vim 2.修改镜像源 将/etc/apt/source.list的原有源注释掉,添加下面的源: deb http://mirrors.163.com/ ...

  4. 查看pip安装的包的位置

  5. Windows10下Linux系统的安装和使用

    WSL 以往我都是直接安装VirtualBox,然后再下载Linux系统的ISO镜像,装到VirtualBox里运行. 改用Win10系统后,了解到了WSL(Windows Subsystem for ...

  6. 关于前端使用JavaScript无法实现canvas打印问题的解决

    当使用浏览器的打印功能window.print()无法打印网页上的canvas图像,但是可以通过转换canvas成一个图片的形式来实现canvas的打印. 解决方法 getElementById获取c ...

  7. js页面--年份自动增加

    ) { document.write("-" + new Date().getFullYear()); }</script> // 大于2017年自动加上 - 年份

  8. 实现api开发实例页面

    主要实现功能: 1.通过点击不同的option选项,自动生成不同的代码. 功能分析: 1.点击不同的option选项,这里其实就是使用了一个事件即onchange,把这个事件放在<select& ...

  9. 吴裕雄--天生自然KITTEN编程:翻译机

  10. linux下光标操作

    Ctrl+左右键    单词间跳转 Ctrl+a    跳到行首 Ctrl+e    跳到行尾 Ctrl+u    删除当前光标前的文字 Ctrl+k    删除当前光标后的文字 Ctrl+w    ...