黑马程序员——C语言基础学习(四)---数组和指针的总结学习

时间:2022-01-28 00:23:58


------  <a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a> 期待与您交流! -------



                  数组和指针的总结学习


.数组

 

  

  1.数组的基本概念

把具有相同类型的若干变量按有序的形式组织起

来.这些按序排列的同类数据元素的集合成为数组.

 

  

  2.数组的几个名词

1)数组:一组具有相同数据类型的数据的有序的集合

2)数组元素:构成数组的数据。数组中的每一个数组元素具有相同

  名称,不同的下标,可以作为单个变量使用,所以也称为下标变量。

3)数组的下标:是数组元素的位置的一个索引或指示。

4)数组的维数:数组元素下标的个数。

  根据数组的维数可以将数组分为一维、二维、 三维、多维数组。 

 

  3.数组分类

(1)按数组元素的类型不同,数组可分为:

数值数组:用来存储数值的

字符数组:用来存储字符 ‘a’

指针数组:用来存放指针(地址)的

结构数组:用来存放一个结构体类型的数据

(2)按维度分类

  一维数组

  二维数组

  多维数组

  

  4.一维数组定义及注意事项

(1)一维数组介绍

如果一个数组的元素不是数组,那么就是一维数组

(2)一维数组的定义

类型说明符 数组名[常量表达式];

(3)数组定义的注意点

1) 数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素

   的数据类型都是相同的。第一个数组元素的地址是数组所占内存块的首地址.

2) 数组名的书写规则应符合标识符的书写规定。 

命名规则

1、只能由字母、数字、下划线组成

2、不能数字开头 

3、不能与关键字重名 

4、严格区分大小写

命名规范: 

1、起一个有意义名字

2、驼峰标示

3) 数组名不能与其它变量名相同。

4) 方括号中常量表达式表示数组元素的个数,如a[5]表示数组a有5个元素。但是

   其下标从0 开始 计算。因此5个元素分别为a[0], a[1], a[2], a[3], a[4]

5) 不能在方括号中用变量来表示元素的个数,但是可以是符号常数或

   量表达式。c99不支持使用变量作为元素个数,llVM编译器,它支持

6) 允许在同一个类型说明中,说明多个数组和多个变量。 

  

  5.一维数组初始化

(1)一维数组的初始化数组初始化赋值是指在数组定义时给数组元素赋予初值。数组初始化是在编译阶

   进行的.这样将减少运行时间,提高效率。初始化方式有两种:定义的同时初始化、先定义,后初始化

(2)定义的同时初始化

类型说明符 数组名[常量表达式] = { 值, 值......值 };

又细分以下几种情况:

1)指定元素的个数的同时,对所有的元素进行显式的初始化 

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

2)指定数组的元素个数,对数组进行部分显式初始化定义的同时对数组

  进行初始化,没有显式初始化的元素,那么系统会自动将其初始化为0

  int nums[10] = {1,2};

3)不指定元素个数,定义的同时初始化,它是根据大括号中的元素的个数来确定数组的元素个数

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

4)指定元素个数,同时给指定元素进行初始化

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

注:给数组赋值的方法除了用赋值语句对数组元素逐个

   赋值外,还可采用初始化赋值和动态赋值的方法

(3)先定义,后初始化

正确写法:

int nums[3];

nums[0] = 1; nums[1]= 2; nums[2] = 3;

错误写法:

int nums[3];

nums ={1,2,3}; // 因为数组名是一个常量,是数组的首地址,所以不能这样赋值. 


  6.一维数组的引用方式

数组元素访问一般形式:

数组名[下标]

数组元素通常也称为下标变量,必须先定义数组,才能使用下标变量. 


  7.一维数组的存储方式

存储方式:

1)计算机会给数组分配一块连续的存储空间

2)数组名代表数组的首地址,从首地址位置,依次存入数组的第1个、第2个....、第n个元素

3)每个元素占用相同的字节数(取决于数组类型)4)并且数组中元素之间的地址是连续 

  

  8.一维数组的地址

1)数组内部的元素地址是连续的

在内存中,内存从大到小进行寻址,为数组分配了存储空间后,数组的

元素自然的从上往下排列 存储,整个数组的地址为首元素的地址。

2)数组名存放的是数组的首地址

数组的首地址:数组的第一个元素首地址(第一个元素的第一个字节地址)

3)数组每个元素的地址

就是每个元素的首地址 

  

  9.计算数组的长度

因为数组在内存中占用的字节数取决于其存储的数据类型和数据的个数

数组在内存中占用的总字节数:sizeof(数组名);

所以计算数组长度可以使用如下方法

数组的长度 = 数组占用的总字节数 / 数组元素占用的字节数 

  

  10.数组的越界

1)越界赋值,越界读取,都是很危险的 

2)一个长度为n的数组,最大下标为n-1, 下标范围:0~n-1 


  11.数组元素作函数实参

数组元素就是下标变量,它与普通变量并无区别.因此它作为函数实参使用与普通变量是完

全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传递 


  12.数组名作为函数参数

1)用数组元素作函数参数不要求形参也必须是数组元素,但是用数组名

  作函数参数时,则要求形参和相对应的实参都必须是类型相同的数组

2)在C语言中,数组名除作为变量的标识符之外,数组名还代表了该数组

  在内存中的起始地址, 因此,当数组名作函数参数时,实参与形参之间

  不是"值传递",而是"地址传递",实参数组名将 该数组的起始地址传

  递给形参数组,两个数组共享一段内存单元,编译系统不再为形参数组

  分配存储单元。

3)在变量作函数参数时,所进行的值传送是单向的。即只能从实参传向

  形参,不能从形参传回实 参。形参的初值和实参相同,而形参的值发

  生改变后,实参并不变化,两者的终值是不同的。 


  13.数组名作函数参数的注意点

1)形参数组和实参数组的类型必须一致,否则将引起错误。

2)形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址

  而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,

  虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,

  这是应予以注意的。

3)在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数

  组元素的个数。 如void nzp( int a[], int n )

4)多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定

  每一维的长度,也可省去第一维的长度。 


  14.冒泡排序思想 

(1)冒泡排序

冒泡排序(Bubble Sort,*译为:泡沫排序或气泡排序)是一种简

的排序算法。它重复地走访过要排序的数列,一次比较两个元素,

如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地

进行直到没有再需要交换,也就是说该数列已经排序完成。这个算

的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。 

(2)冒泡排序步骤(大数下沉)

1)比较相邻的元素。如果第一个比第二个大,就交换他们两个。

2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最

  后对。在这一点,最后的元素应该会是最大的数。

3)针对所有的元素重复以上的步骤,除了最后一个。

4)持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。 


  15.冒泡排序的实例:

#include <stdio.h>

void BubbleSort(int *array, int length) {
// temp
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - 1 - i; j++) {
if(array[j] > array[j+1]) {
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}
}


void printArray(int *array, int length) {
for (int i = 0; i < length; i++) {
printf("%d\n", array[i]);
}
}



int main(int argc, const char * argv[]) {
int nums[] = { 1, 3, 5, -2, 4, 98, -100, 35 };
int length = sizeof(nums) / sizeof(int);
printArray(nums, length);

BubbleSort(nums, length);

printArray(nums, length);
return 0;
}





  16.选择排序思想 

(1)选择排序

选择排序(Selection sort)是一种简单直观的排序算法.它的工作原

理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始

位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序

序列末尾。以此类推,直到所有元素均排序完毕。

(2)选择排序基本思想

第一趟排序在所有待排序的n个记录中选出关键字最小的记录,将它与

数据表中的第一个记录交换位置,使关键字最小的记录处于数据表的

最前端;第二趟在剩下的n-1个记录中再选出关键字最小的记录,将其

与数据表中的第二个记录交换位置,使关键字次小的记录处于数据表

的第二个位置;重复这样的操作,依次选出数据表中关键字第三小、第

四小...的元素,将它们分别换到数据表的第三、第四...个位置上。

排序共进行n-1趟,最终可实现数据表的升序排列。 

  17.选择排序实例

#include <stdio.h>

int main(int argc, const char * argv[]) {
int nums[] = { 12, 3, 40, 25, 7 };
int length = sizeof(nums) / sizeof(int);

for (int j = 0; j < length - 1; j++) {
for (int i = j + 1; i < length; i++) {
if(nums[j] > nums[i]) {
int temp = nums[j];
nums[j] = nums[i];
nums[i] = temp;
}
}

}



for (int j = 0; j < length - 1; j++) {
for (int i = j + 1; i < length; i++) {

}

}
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - 1 - i; j++) {


}
}

return 0;
}





.指针


  1.地址

(1)计算机硬件系统的内部存储器中,拥有大量的存储单元(容量为1字节).为了方便管理,必须

   为每一个存储单元编号,这个编号就是存储单元的地址.每个存储单元都有一个唯一个地址.

   内存地址----->内从中存储单元的编号.

(2)在地址所标识的存储单元中存放数据。

   注:内存单元的地址与内存单元中的数据是两个完全不同的概念。

      变量地址── 系统分配给变量的内存单元的起始地址 


  2.指针的基本概念

内存单元的编号也叫做地址.既然根据内存单元的编号或地址就可以找到所需的内存单,

所以通常也把这个地址称为指针.内存单元的指针和内存单元的内容是两个不同的概念。

总结:对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容

 

  3.使用指针好处

a.为函数提供修改调用变量的灵活手段;

b.让函数有多个返回值

c.可以改善某些子程序的效率>>在数据传递时,如果数据块较大(比如说数据缓冲区或比较大

  的结构),这时就可以使用指针传递地址而不是实际数据,即提高传输速度,又节省大量内存。

d.为动态数据结构(如二叉树、链表)提供支持 


  4.变量的存取方式

存取方式分为两种:

直接存取:变量的赋值和取值(通过变量名进行存取值)

间接存取:通过指针(地址)间接操作完成

 

  5.指针变量的概念

C语言中,允许用一个变量来存放指针,这种变量称为指针变量.因此,

个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

注: 严格意义上说,指针是一个地址,是一个常量针变量是存放一个地址,是一个


  6.定义一个指针变量

对指针变量的定义包括三个内容:

 1)指针类型说明,即定义变量为一个指针变量;

 2)指针变量名;

 3)变量值(指针) 

类型说明符 *变量名; 

注意: 

1)在定义指针时,“*”号表示定义的变量是指针变量,变量的值只能存放地址。这时

  候的'*' 没有取地址内容的意思,只是一个类型,所以初始化的时候还是接收地址.

2)一个类型的指针只能指向同类型的变量,不能指向其他类型的变量。 

3)指针也可以被声明为全局、静态局部和局部的。 


  7.指针变量的初始化和引用

(1)指针变量的初始化方法

1)定义的同时进行初始化

  int a = 5; int *p = &a; 

2)先定义后初始化

  int a; int *p;p=&a; 

3)把指针初始化为NULL

  int*p=NULL; 或 int *q=0; 

注意点:

1、多个指针变量可以指向同一个地址 

2、指针的指向是可以改变的 

3、指针没有初始化里面是一个垃圾值,这时候我们这是一个野指针, 

如果操作一个野指针 

1)可能会导致程序崩溃

2)访问你不该访问数据 


所以指针必须初始化才可以访问其所指向存储区域

(2)使用“*”获取指针对应存储区域的内容


C语言中提供了地址运算符&来表示变量的地址。指针运算符 *来读取地址里面的内容. 


其一般形式为: 


    &变量名; *指针变量名 


关于*的使用注意:


1)在定义变量的时候 * 是一个类型说明符,说明定义的这个变

        量是一个指针变量 ,没有提取地址内容的意思


2)在不是定义变量的时候 *是一个操作符,访问指针所指向存储空间 

  8.指针常见的应用场景

1)在函数中间接访问调用者中的变量

void change(int *num){

*num = 10;

}

 2)函数返回多个值

//写个一个函数,计算两个数的和、差、乘积、商

voidsumAndMinusAndJiAndShang(int num1,int num2,int *sum,int *minus,int *ji,int*shang)

{

*sum = num1 + num2;

 *minus = num1 - num2;

*ji = num1 * num2;

 *shang = num1 / num2;

}

  9.二级指针概述 

如果一个指针变量存放的又是另一个指针变量的地址,则称

这个指针变量为指向指针的指针变量。也称为“二级指针”

  10.在多级换算的时候的规律 

我们要记住,指针是用来存储地址的就可以了.

可以总结为 :

指针变量名 == &所指向的的变量

例如 p = &a; 然后指针上面加一个* 它所指向的变量就减去一个&.

例如 p ==&a;左边加一 个* 右边就去掉一个 & 就是*p == a; 

那么我们来举一个例子.**pp怎么换算呢? 

首先 pp == &p 

那么**pp == **(&p); *(&p) == p; 

那 么**pp ==**(&p) == *p ;

然后p == &a,所以**pp == **(&p) == *p == *(&a),

因为*(&a)== a;

那 么**pp == **(&p) == *p == *(&a) == a ,

也就是**pp ==a.


  11.数组指针

指向一个数组的指针就是数组指针.

数组指针其实就是数组的首地址.

 

数组元素指针

一个变量有地址,一个数组包含若干元素,每个数组元素都

有相应的地址.所谓数组元素指针就是数组元素的地址.

 

注意:

数组名a不代表整个数组,只代表数组首元素的地址。

“p=a;”的作用是“把a数组的首元素的地址赋给

指针变量p”,而不是“把数组a各元素的值赋给 p”


  12.使用指针引用数组元素

在指针指向数组元素时,允许以下运算:
加一个整数(用+或+=),如p+1
减一个整数(用-或-=),如p-1
自加运算,如p++,++p
自减运算,如p--,--p
 
两个指针相减,如p1-p2(只有p1和p2都指向同一数组中的元素时才有意义)
每次相加相减都是以自身类型所占用的字节数来移动地址的.
例如:int型 p +2 就是移动两个4字节 .


     注意:   

1)如果p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址
2)*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]
3)如果指针p1和p2都指向同一数组
 
结论:
引用一个数组元素,可用下面两种方法:
(1)下标法,如a[i]形式
(2)指针法,如*(a+i)或*(p+i)收地
 
注:a是常量(a++错误),p是变量(p++正确) 


  13.一维指针数组介绍


一个数组的元素值为指针则是指针数组.


形式:类型说明符 *数组名[数组长度]

例:int *pa[3]

表示pa是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。

 

  14.指针变量之间运算

(1)两指针变量减法运算

两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数*类型所占字节数.

指针之间的算术运算

(pointer2地址值-pointer地址值)/sizeof(所指向数据类型) ==两者相差的元素数

(2)两指针变量进行关系运算

指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系(先后顺序,高低顺序等)。

指针变量还可以与0比较.设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。

注意:

指针之间可以相减,但不可以相加(相加无意义)

空指针是由对指针变量赋予0值而得到的。 


  15.二维数组指针的定义、初始化

 

数据类型 (*指针变量名)[二维数组列数];

例 int a[4][5]; int (*p)[5] = a;

 

如要将二维数组赋给一指针,应这样赋值:

int a[3][4];

int(*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。

p=a;//将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]

p++;//该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针,每次加i都是移动一

行的地址的大小,而非移动一个元素的地址大小

 

  16.指针数组和二维数组指针变量的区别

int*pa[3]={&a,&b,&c}; // pa是一个指针数组

int*pa1[2]={a[0],a[1]} // pa1是一个指针数组

int(*pa)[3]; // 二维数组指针

应该注意指针数组和二维数组指针变量的区别。这两者虽

都可用来表示二维数组,但是其表示方法和意义是不同的。


int(*p)[3]; 表示一个指向二维数组的指针变量。该二维数组的列数为3

分解为一维数组的长度为3。p每加1移动的是(列数*int类型长度)个地址.

int *p[3];表示p是一个指针数组,有三个下标变量p[0],p[1],p[2]均为指针

变量。p每加1移动的 是(一个指针变量的大小)个地址.

 

  17.字符串指针介绍及使用

(1)字符串指针

在C语言中,可以用两种方法访问一个字符串

1)字符数组

2)字符串指针指向字符串

char *变量名="字符串内容";

字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的。只能 按对指针变量的赋值不同来区别. 对指向字符变量的指针变量应赋予该字符变量的地址.

如:

char c,*p=&c;

表示p是一个指向字符变量c的指针变量。

而:

char *s="CLanguage";

则表示s是一个指向字符串的指针变量。把字符串的首地址赋予s。

3)字符串指针的定义和初始化

定义的同时进行初始化

char *ps="CLanguage";

等效于:

char *ps;

ps="CLanguage";

 

注意:

1、使用字符数组来保存的字符串是保存在栈里的,保存栈里面的东西是

   读可写,所有我们可以改变里面的字符当把一个字符串常量赋值一个字

   符数组的 时候,那么它会把字符串常量中的每一个字符都放到字符数组里面.

2、使用字符指针来保存字符串,它保存的是字符串常量地址,常量区是只

   读的, 所以我们不可以修改字符串中的字符.

 

(2)字符串指针使用注意

1)可以利用指针的加减来查看字符串的每一个字符. 例:*(p+1)

2)不可以修改字符串内容

3)妙用字符串指针

将指针变量指向一个格式字符串,用在printf函数中,用于

输出二维数组的各种地址表示的值。但在printf语句中用

指针变量PF代替了格式串。这也是程序中常用的方法

例:

static inta[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};

char *PF;

PF="%d,%d,%d,%d,%d\n";

printf(PF,a,*a,a[0],&a[0],&a[0][0]);

printf(PF,a+1,*(a+1),a[1],&a[1],&a[1][0]);

printf(PF,a+2,*(a+2),a[2],&a[2],&a[2][0]);

printf("%d,%d\n",a[1]+1,*(a+1)+1);

printf("%d,%d\n",*(a[1]+1),*(*(a+1)+1));

4)不能够直接接收键盘输入

错误的原因是:没有初始化就直接在scanf里面接收字

符串,这个时候指针是一个野指针(就算是初始化为NULL

也是一样),他并没有指向某一块内存空间,所以不允许

这样写如果给str分配内存空间是可以这样用的 


  18.指针应用举例:

    例1:

#include <stdio.h>

/*
将指针变量作为函数参数传递应用

*/


void func(int *p) {

*p *= 2;
}




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

int num = 123;




printf("&num = %p\n", &num);
printf("num = %d\n", num);


func(&num);

printf("num= %d\n", num);


return 0;
}


  例2

/*

使用指针作为函数参数可以返回多个值应用

*/

#include <stdio.h>

void inputIntNum(int *num) {


int count;
while(1) {
printf("请输入数字\n");
count = scanf("%d", num);
while (getchar() != '\n');
if(count > 0) break;

printf("输入的不是数字, ");
}
}



char czInput(int *num) {
// 如果用户输入的是数字, 那么就将数字存储在 *num 中
// 如果用户输入的不是数字, 那么将第一个字符返回
// 如果输入的时数字, 则返回 '\0'
char ch = '\0';
int count;

printf("请输入\n");
count = scanf("%d", num);
if( count == 0 ){
// 输入失败
ch = getchar();
}
if(ch != '\n') {
while (getchar() != '\n');
}

return ch;
}

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


int num;
char ch;


if( (ch = czInput(&num)) != '\0' ) {
// 输入的是字母
printf("输入的第一个字母是 %c\n", ch);
} else {
// 输入数字
printf("输入的数字是 %d\n", num);
}


return 0;
}