目录
数组
一维数组
类型说明符 数组名[整型常量表达式] (申请的是连续空间)
int a[4] = {0};//整型数组a[0]~a[3],清空数组(未赋值的,自动赋值为零)
char b[4] = {'h','e','l','l','o'};//字符数组
// 初始化时局部变量为随机数,全局变量为0
初始化只能在定义的时候执行
// 正常初始化
int a[5] = {100,200,300,400,500};
int a[5] = {100,200,300,400,500,600}; // 错误,越界了
int a[ ] = {100,200,300}; //自动根据初始化列表分配数组元素个数
int a[10] = {1,2,3,4,5};//部分初始化,未初始化的自动初始化为0
//下面是错误初始化
int a[5];
a[5] = {1,2,3,4};//a[5]未知空间且只是单个空间,只能赋值。
a = {1,2,3,4,5};//不能对数组名a赋值。
数组偏移
数组的下标地址与首地址的偏移量有关,与数组的大小无关
注释:可以理解为和数据类型说明符无关,无论int、char还是其他类型,
// 比如, a[3]等价于a的基地址+偏移量2个(int单位)
printf("a[2] addr :%p,%p,%p\n",&a[2],a+2,&a[0]+2);
变长数组(不是真的变长,只是定义数组前,变量所赋初值为可变值)
int size = 10;
// 不能在使用变长数组的情况下初始化数组
int a[size] = {1,2,3};//编译出错
// 变长数组只能先定义再使用
int a[size]; // 正确的
a[0] = 10;
获取数组长度
int numbers[] = {1, 2, 3, 4, 5};
int length = sizeof(numbers) / sizeof(numbers[0]);
多维数组(二维数组举例)
二维数组的取地址及内容输出。
[]的作用就是解引用array[0]==*(array+0)
#include<stdio.h>
int main()
{
int a[3][5]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14};
printf("%d %p\n",a[2][3],&a[2][3]); //方法一
printf("%d %p %p %p\n",a[2][0],&a[2][0],*(a+2),*(&a[2]+0)); /**(a+2)==a[0+2][0]*/
printf("%d %p %p %p\n",a[0][0],&a[0][0],*(a+0),*(&a[0]+0));
}
//结果
// 13 0x7ffffcc04
// 10 0x7ffffcbf8 0x7ffffcbf8 0x7ffffcbf8
// 0 0x7ffffcbd0 0x7ffffcbd0 0x7ffffcbd0
数组注释:
为什么数组下标从0开始
因为数组名就是这个数组的首元素的地址,所以从0开始是为了不让首元素地址产生偏移从而导致数据丢失因为*(Array+0)
其实就是Array[O]
,在c编译的时候所有的[]
都会被转换成*()
,同时数组名也可以代表整个数组
// * 表示将地址里面的内容取出,我们把它称为解引用
//&(buf+1)如果带取地址符&表示整个数组的地址,如果buf为二维数组则表示&取整个二维数组的地址,否则为取首元素地址
char Array[5] = {'j','a','c','k'};//一维数组
printf("%c%c%c%c%c\n",Array[0],&Array[0],*(Array+0),*(&Array[0]),0+[array]);
解引用数组名取出内容,而数组名里的内容是地址,对数组名取地址取得数组名的地址而不是内容
int a[2][3]={{1,2,3}, {4,5,6}};//非线性,{}后会自动补上'\0'。
//int a[2][3]={1,2,3,4,5,6};线性关系
//a[2] 是数组的定义,表示该数组拥有两个元素
//int [3]是元素的类型,表示该数组元素是一个具有三个元素的整 型数组(该数组一共有2行,每行由3个元素组成的数据类型 二维数组名[行][列])
指针
一级指针
指针的概念: 一个专门用来存放内存地址的变量,指针也就是指针变量
地址:比如 &a 是一个地址,也是一个指针,&a 指向变量 a,专门用于存储地址的变量,又称指针变量。
指针的尺寸指的是指针所占内存的字节数 ,指针所占内存,取决于地址的长度,而地址的长度则取决 于系统寻址范围,即字长 。
在32位系统,指针的大小占用4字节
在64位系统,指针的大小占用8字节
//指针定义
// 用于存储 int 型数据的地址,p1 被称为 int 型指针,或称整型指针
int *p1;
// 用于存储 char 型数据的地址,p2 被称为 char 型指针,或称字符指针
char *p2;
// 用于存储double型数据的地址,p3 被称为 double 型指针
double *p3;
//指针赋值:赋给指针的地址,类型需跟指针的类型相匹配。
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;
多级指针
如果一个指针变量p1存储的地址,是另一个普通变量a的地 址,那么称p1为一级指针
如果一个指针变量p2存储的地址,是指针变量p1的地址,那 么称p2为二级指针
如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址, 那么称 p3 为三级指针
以此类推,p2、p3等指针被称为多级指针
int a = 100;
int *p1 = &a; // 一级指针,指向普通变量
int **p2 = &p1; // 二级指针,指向一级指针
int ***p3 = &p2; // 三级指针,指向二级指针
指针万能拆解法
任意的指针,不管有多复杂,其定义都是由两部分组成
-
第一部分:指针所指向的数据类型,可以是任意类的类型
-
第二部分:指针的名字
char (*p1); // 第2部分:*p1; 第1部分:char;
char *(*p2); // 第2部分:*p2; 第1部分:char *;
char **(*p3); // 第2部分:*p3; 第1部分:char**;
char (*p4)[3]; // 第2部分:*p4; 第1部分:char[3];
char (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);
上述示例中,p1、p2、p3、p4、p5本质上并无区别,它们均是指针
上述示例中,p1、p2、p3、p4、p5唯一的不同,是它们所指向的数据类型不同
第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边
野指针
概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。 (定义且暂时不用的需先指向NULL)
危害:
- 引用野指针,相当于访问了非法的内存,常常会导致段错误
- 引用野指针,可能会破坏系统的关键数据,导致系统崩溃 等严重后果
产生原因:
-
指针定义之后,未初始化
-
指针所指向的内存,被系统回收
-
指针越界
如何防止:
-
指针定义时,及时初始化
-
绝不引用已被系统回收的内存
-
确认所申请的内存边界,谨防越界
空指针
概念:空指针即保存了零地址的指针,亦即指向零地址的指 针。
NULL地址其实就是 (void *)0,就是0x0000 0000
// 1,刚定义的指针,让其指向零地址以确保安全:
char *p1 = NULL;
int *p2 = NULL;
// 2,被释放了内存的指针,让其指向零地址以确保安全:
char *p3 = malloc(100); // a. 让 p3 指向一块大小为
100个字节的内存
free(p3); // b. 释放这块内存,此时 p3
相当于指向了一块非法内存
p3 = NULL; // c. 让 p3 指向零地址
指针运算
int a = 100;
int *p = &a; // 指针 p 指向整型变量 a
int *k1 = p + 2; // 向上移动 2 个目标(2个int型数据)
int *k2 = p - 3; // 向下移动 3 个目标(3个int型数据)
// 几乎不用,容易出现越界
int (*q)[5] = &a;
printf("%p\n",(q+1));// 越界(数组名表示数组整体)
printf("%d\n",*(q+1));
*与 &是互逆运算
int a = 10;
int *p;
//int *p = &a; //*单独使用为解引用,所以 *p =&a; 错误
p = &a;
printf("*(&a) value:%d\n",*(&a));// * 与 &是互逆运算
*p = 300; //*p 此时相当于变量a
数组指针
数组的指针的本质为指,此指针保存的是数组的地址,说白了就是指针指向数组名,此类指针称为数组指针 。
int arr[3][4] = {{1,2,3,4},{10,20,30,40},
{11,22,33,44}};
定义一个指针p存储数组的名字//数组指针 int a = 5;
int (*p)[4] = arr;
通过arr p 将所有获取到1的数据的方法罗列出来
通过arr p 将所有获取到20的数据方法罗列出来
int (*p)[4] = arr;
int (*q)[3][4] = &arr;
int arr[3][4] = {
{1,2,3,4},
{10,20,30,40},
{11,22,33,44}
};
//定义一个指针p存储数组的名字//数组指针
int (*p)[4] = arr;
//通过arr p 将所有获取到1的数据的方法罗列出来
printf("arr[0][0] = %d\n",p[0][0]);
printf("arr[0][0] = %d\n",(*(p+0))[0]);
printf("arr[0][0] = %d\n",*((*(p+0))+0));
printf("arr[0][0] = %d\n",**p);
//通过arr p 将所有获取到20的数据方法罗列出来
printf("arr[1][1] = %d\n",p[1][1]);
printf("arr[1][1] = %d\n",(*(p+1))[1]);
printf("arr[1][1] = %d\n",*((*(p+1))+1));
指针数组
指针数组的本质为数组,数组里面存放的内容为指针,而一般指针是指向的地址为字符串居多,我们把此类型称为指针数组。
char *p[5];
char *p = "jack";
char *p[5] = {"jack","rose","ken","tony","tom"};
printf("%s\n",p[2]);
printf("%c\n",p[3][3]);
//"hello"是字符串常量,同时"hello"也是一个匿名数组的空间首地址
char *str[3] = {"abc","def","hij"};
字符串与指针
字符串常量在内存中实际就是一个匿名数组
匿名数组满足数组的两个条件
第一个含义,表示整个数组
第二个含义,表示首元素地址
char buf[] = "abcd";
printf("%c\n",buf[1]);
printf("%c\n","abcd"[1]);
char *p = "abcd"; // 将p指向一块匿名数组的一个首地址
printf("%p,%p\n","abcd",&"abcd"[0]);
// 讲"jack"字符串存放在buf的空间里面
char buf[] = "jack";
// 定义指针p指向buf的首地址
char *p = buf;
// 定义指针q指向"jack"的首地址
char *q = "jack";
printf("%s,%s,%s,%d\n",buf,p,q,sizeof(q));
附录
字节序
大端模式:高位数据存放在内存的低地址端,低位数据存 放在内存的高地址端
小端模式:高位数据存放在内存的高地址端,低位数据存 放在内存的低地址端
int a = 0x11223344;
char *p = (char *)&a;
if(*p == 0x11)
printf("大端模式\n");
else
printf("小端模式\n");
段错误原因:没提供空间,访问非法空间,访问常量区修改常量
未完待续,发现错误请留言指正(万分感谢)。