C语言零基础--初级数组+指针--系统学习4day

目录

1.数组的基本概念

2.数组元素的应用

3.字符数组的引用

4.多维数组的引用

5.数组万能拆分法

6.内存地址的引入

7. 基地址的讲解

8.取址符的引用

9.指针的引入

10.野指针*的误区

11.空指针

12.指针运算

13.指针+数组问题讲解

(1).数组是不是就是地址

(2).指针取地址

(3).数组及数组元素地址

(4) .数组及指针定义

(5).数组的基本操作

(6).数组基本操作


1.数组的基本概念

        逻辑:一次性定义多个相同类型的变量,并存储到一片连续的内存中

        示例:

int a[5];

        语法:

        a 是数组名,即这片连续内存的名称 [5] 代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素

        int 代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组

        初始化:在定义的时候赋值,称为初始化         

// 正常初始化
int a[5] = {100,200,300,400,500};

int a[5] = {100,200,300,400,500,600}; // 错误,越界了
int a[ ] = {100,200,300}; // OK,自动根据初始化列表分配数组元素个数
int a[5] = {100,200,300}; // OK,只初始化数组元素的一部分

2.数组元素的应用

        存储模式:一片连续的内存,按数据类型分割成若干相同大小的格子

        元素下标:数组开头位置的偏移量

        示例:

int a[5]; // 有效的下标范围是 0 ~ 4
a[0] = 1;
a[1] = 66;
a[2] = 21;
a[3] = 4;
a[4] = 934;

a[5] = 62; // 错误,越界了
a    = 10; // 错误,不可对数组名赋值 

3.字符数组的引用

        概念:专门存放字符的数组,称为字符数组

        初始化与元素引用

char s1[5] = {'a', 'b', 'c', 'd', 'e'};       // s1存放的是字符序列,非字符串
char s2[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; // s2存放了一个字符串

char s[6] = {"abcde"}; // 使用字符串直接初始化字符数组
char s[6] =  "abcde" ; // 大括号可以省略

s[0] = 'A'; // 索引第一个元素,赋值为 'A'

4.多维数组的引用

        概念:若数组元素类型也是数组,则该数组称为多维数组 

        示例:(类比矩阵)

int a[2][3];

// 代码释义:
// 1, a[2]   是数组的定义,表示该数组拥有两个元素
// 2, int [3]是元素的类型,表示该数组元素是一个具有三个元素的整型数组

多维数组的语法跟普通的一维数组语法完全一致

         初始化

int a[2][3] = {{1,2,3}, {4,5,6}}; // 数组的元素是另一个数组

int a[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 错误,越界了
int a[2][3] = {{1,2,3}, {4,5,6,7}};        // 错误,越界了

int a[ ][3] = {{1,2,3}, {4,5,6}}; // OK,自动根据初始化列表分配数组元素个数
int a[2][3] = {{1,2,3}};          // OK,只初始化数组元素的一部分

         引用:

// a[0] 代表第一个元素,这个元素是一个具有 3 个元素的数组:{1,2,3}
// a[1] 代表第二个元素,这个元素也是一个具有 3 个元素的数组:{4,5,6}

printf("%d", a[0][0]); // 输出第一个数组的第一个元素,即1
printf("%d", a[1][2]); // 输出第二个数组的第三个元素,即6

5.数组万能拆分法

        任意的数组,不管有多复杂,其定义都由两部分组成。
        第1部分:说明元素的类型,可以是任意的类型(除了函数)
        第2部分:说明数组名和元素个数

        示例:

int   a[4];       // 第2部分:a[4]; 第1部分:int
int   b[3][4];    // 第2部分:b[3]; 第1部分:int [4]
int   c[2][3][4]; // 第2部分:c[2]; 第1部分:int [3][4]

//数组指针,叫法根据优先级。优先级  [] > *
int  *d[6];       // 第2部分:d[6]; 第1部分:int *

//指针数组函数  值是函数地址,就是数组的元素指向函数地址
int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)

 上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]本质上并无区别,它们均是数组。
 上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]唯一的不同,是它们所存放的元素的不同。
第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边。

6.内存地址的引入

        字节:字节是内存的容量单位,英文称为 byte,一个字节有8位,即 1byte = 8bits
        地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址

7. 基地址的讲解

单字节数据:对于单字节数据而言,其地址就是其字节编号。
多字节数据:对于多字节数据而言,其地址是其所有字节中编号最小的那个,称为基地址。

8.取址符的引用

每个变量都是一块内存,都可以通过取址符 & 获取其地址

int a = 100;
printf("整型变量 a 的地址是: %p\n", &a);

char c = 'x';
printf("字符变量 c 的地址是: %p\n", &c);

double f = 3.14;
printf("浮点变量 f 的地址是: %p\n", &f);

 注意:虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸确实一样的。
            不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。

9.指针的引入

        指针概念:地址,比如 &a 是一个地址,也是一个指针,&a 指向变量 a。 专门用于存储地址的变量,又称指针变量。 

        指针定义

int    *p1; // 用于存储 int  型数据的地址,p1 被称为 int  型指针,或称整型指针
char   *p2; // 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针
double *p3; // 用于存储double型数据的地址,p3 被称为 double 型指针
//指针的赋值:赋给指针的地址,类型需跟指针的类型相匹配。
int a = 100;
p1 = &a; // 将一个整型地址,赋值给整型指针p1

char c = 'x';
p2 = &c; // 将一个字符地址,赋值给字符指针p2

double f = 3.14;
p3 = &f; // 将一个浮点地址,赋值给浮点指针p3

  

         指针的索引:

*p1 = 200; // 将 p1 指向的目标(即a)修改为200,等价于 a = 200;
*p2 = 'y'; // 将 p2 指向的目标(即c)修改为'y',等价于 c = 'y';
*p3 = 6.6; // 将 p3 指向的目标(即f)修改为6.6,等价于 f = 6.6;

         指针的尺寸:指针所占内存的字节数 指针所占内存,取决于地址的长度,而地址的长度则取决于系统寻址范围,即字长
        结论:指针尺寸只跟系统的字长有关,跟具体的指针的类型无关

10.野指针*的误区

        概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。

         产生原因:指针定义之后,未初始化 || 指针所指向的内存,被系统回收 ||指针越界

         危害:引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
                    引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果

         预防:指针定义时,及时初始化 。绝不引用已被系统回收的内存 。确认所申请的内存边界,谨防越界

11.空指针

        很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。

        一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。

        概念:空指针即保存了零地址的指针,亦即指向零地址的指针。

// 1,刚定义的指针,让其指向零地址以确保安全:
char *p1 = NULL;
int  *p2 = NULL;

        代码建议:

// 2,被释放了内存的指针,让其指向零地址以确保安全:
char *p3 = malloc(100); // a. 让 p3 指向一块大小为100个字节的内存
free(p3);               // b. 释放这块内存,此时 p3 相当于指向了一块非法内存
p3 = NULL;              // c. 让 p3 指向零地址

12.指针运算

指针加法意味着地址向上移动若干个目标
指针减法意味着地址向下移动若干个目标

int  a = 100;
int *p = &a; // 指针 p 指向整型变量 a

int *k1 = p + 2; // 向上移动 2 个目标(2个int型数据)
int *k2 = p - 3; // 向下移动 3 个目标(3个int型数据)

 

13.指针+数组问题讲解

(1).数组是不是就是地址

答:有时候是,有时候不是。在C语言中非常重要的一点是:同一个符号,在不同场合,有不同的含义。 比如数组 int a[3];当出现在以下三种情形中的时候,它代表的是一块12字节的内存:

        1.初始化语句时:int a[3];

        2.与sizeof结合时:sizeof(a)
        3.与取址符&结合时:&a
       只有在上述三种情形下,数组a代表一片连续的内存,占据12个字节,而在其他任何时候,数组a均会被一律视为其首元素的地址。
        因此,不能武断地说数组是不是地址,而要看它出现的场合。

(2).指针取地址

指针不是地址码?为什么还可以取地址?地址的地址是什么意思?

答:首先需要明确,指针通常指指针变量,是一块专用于装载地址的内存,因此指针跟别的普通变量没什么本质区别,别的变量可以取地址,那么指针变量当然也可以取地址。

(3).数组及数组元素地址

假如有如下定义:int a[3][5]; 完成如下要求:

用1种方法表示 a[2][3] 的地址。
用2种完全等价的方法表示 a[2][0] 的地址。
用3种完全等价的方法表示 a[0][0]的地址。
解析: 第一问直截了当,a[2][3]的地址:

1. &a[2][3]

2. &a[2][0]
2. a[2]

3. &a[0][0]
3. a[0]
3. *a

a[0] 等价于 *(a+0) 等价于 *(a) 等价于 *a

(4) .数组及指针定义

请写出符合以下要求的定义语句。

定义一个整型数 i
定义一个指向整型数的指针 p
定义一个指向整型指针的指针 k
定义一个有 3 个整型数的数组 a
定义一个有 3个整型指针的数组 b
定义一个指向有 3 个整型元素的数组的指针 q
定义一个指向函数的指针 r,该函数有一个整型参数并返回一个整型

        参考代码:

1. int i;
2. int *p;//指向变量地址
3. int **k;//指向一级指针地址
4. int a[3];//保存变量值
5. int *b[3];//数组指针,指向变量地址
6. int (*q)[3];//指针数组  保存数组地址值
//返回值 int   参数:int
8. int (*r)(int);//函数指针  保存函数地址  以便调兵遣将

(5).数组的基本操作

        编写一个函数,接收三个类型相同的整型数组 a、b 和 c,将 a 和 b 的各个元素的值相加,存放到数组 c 中。

        参考代码: 

#include <stdio.h>

#define LIM 4

//这里不用返回值,数组名本就是指针=地址,改变的值会保留
void sumary(int array1[], int array2[],
	    int array3[], int size){
	int i;

	for(i=0; i<size; i++)
		array3[i] = array1[i] + array2[i];
}//of sumary

int main(void){

	int array1[LIM] = {2, 4, 6, 8};
	int array2[LIM] = {1, 0, 3, 6};
	int array3[LIM];

	sumary(array1, array2, array3, LIM);
	
	int i;
	for(i=0; i<LIM; i++){
		printf("%d\t", array3[i]);//打印
	}//of for
	printf("\n");
	return 0;
}//of main

(6).数组基本操作

【6】编写一个程序,不使用格式控制符 %x 的情况下,将十进制数转换为十六进制。

解析:假定有一个十进制数为123,转换为十六进制的思路是将123对16进行短除法,每次取余数放入一个数组中,并将商作为新的十进制数,重复以上过程直到商为0。最后,将数组中的数据倒序输出就是结果。需要注意的地方有几点:

第一,数组必须可以存储字母,因为十六进制数包含字母;

第二,倒序输出结果。
 

//编写一个程序,不使用格式控制符 %x 的情况下,将十进制数转换为十六进制。
//十进制转十六进制,做除法余数取倒为结果(10-15取A-F),直到商为0
#include <stdio.h>

void fun(int num){
    int num1=num;
    if(num<0){
        num1=-1*num;
    }
    char hex[10];
    int i;
    for(i=0;i<10&&num!=0;i++){
        switch(num%16){
            case 0 ... 9:
            hex[i]=num%16+'0';
            break;
            case 10 ...15:
            hex[i]=num%16-10+'A';
            break;
        }
        num=num/16;
    }
     if(i >= 10){
        printf("数字太大,无法计算\n");
        return 0;
    }
    printf("num转换为十六进制:0x");
    for(int j=i-1;j>=0;j--){
        printf("%c",hex[j]);
    }

}
int main(){
    int num;
    printf("请输入一个十进制数: ");
    scanf("%d", &num); // 为了突出重点,此处未进行输入合法性检测,望读者知悉
    fun(num);
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西柚小萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值