赞
踩
目录
- 数据类型 变量名 [ 数量 ] ;
- int a[5];
int a [5] ;
初始化:
只有在定义的过程中顺便赋值,称为初始化。只有在初始化的时候可以对这个数组进行连续赋值。
- int a [5] = {1,2,3,4,5} ; // 初始化
- int a [5] ; // 可行,但是该数组中的数据内容是不确定的
- int a[5] = {100,200,300,400,500,600}; // 警告,越界了,编译器会把越界部分直接放弃 600
- int a[ ] = {100,200,300}; // OK,自动根据初始化列表分配数组元素个数
- int a [] ; // 【错误】数组在定义申请内存的过程中 【无法确定内存大小】
- int a[5] = {100,200,300}; // OK,只初始化数组元素的一部分,未初始化部分被默认设置为0
a[0] = 6; a[1] = 7;
数组元素的引用
元素下标偏移量
- 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; // 错误,不可对【数组名】赋值
- 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" ; // 大括号可以省略【字符串】
- char s[5] = "abcde" ; // 【字符序列】
-
- s[0] = 'A'; // 索引第一个元素,赋值为 'A'
- int a[2][3];
-
- // 代码释义:
- // 1, a[2] 是数组的定义,表示该数组拥有两个元素
- // 2, int [3]是元素的类型,表示该数组元素是一个具有三个元素的整型数组
二维数组的初始化:
- int a[2][3] ; // 定义了二维数组但是没有进行初始化,因此数组中的数据是随机的。
- int a[2][3] = { {1,2,3} , {9,8,7} }; // 初始化整个数组
- int a[2][3] = { {1,2} , {9,8} }; // 初始化二维数组中的小素组的的时候可以不完全初始化
- int a[2][3] = { 1,2,3 ,9 , 8 , 7} ; // 初始化整个数组 (按顺序进行初始化)
-
- int a[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 错误,越界了, 多出来的 {7,8,9} 会被编译器丢弃
-
- int a[2][3] = {{1,2,3}, {4,5,6,7}}; // 错误,越界了 , 多出来的 7 会被编译器丢弃
-
- int a[ ][3] = {{1,2,3}, {4,5,6}}; // OK,自动根据初始化列表分配数组元素个数 a[2]
- int a[2][ ] = {{1,2,3}, {4,5,6}}; // [错误] int [] 类型不完成无法正确分配内存空间
- int a[2][3] = {{1,2,3}}; // OK,只初始化数组元素的一部分
打怪实战
- 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)
注解:
- 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);
注意:
- char * p1; // 尺寸为 8字节, 指向的内存应该是1个字节的
- short * p2; // 尺寸为 8字节, 指向的内存应该是2个字节的
- int * p3; // 尺寸为 8字节, 指向的内存应该是4个字节的
- long * p4; // 尺寸为 8字节, 指向的内存应该是8个字节的
理解:
& 取地址符 --> 取得某一个变量的地址
* 解引用符 --> 去地址(去到某一个地址中访问数据)
语法:
指针指向的类型 * 指针变量名 ;
- 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;
小怪实战
- // 使用二维数组实现随机点名
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- #include <unistd.h>
-
- int main(int argc, char const *argv[])
- {
-
- char NameS[100][32] = {
- "Even", // NameS[0]
- "Jacy", // NameS[1]
- "TieZhu",
- "ErGou",
- "CuiHua",
- "DaChui", // NameS[5]
- "ZhangSan" , // NameS[6]
- "Yilia",
- "WangDaChui",
- "TieDan",
- };
-
-
- // for (int i = 0; i < 3 ; i++)
- // {
- // printf("请输入一个姓名:\n");
- // scanf("%s" , NameS[i+7]);
- // }
-
-
- // 设置随机种子
- srand((int)time(0));
- int index ;
-
- // 每间隔0.3秒出现一个名字
- for (int i = 0; i < 10; i++)
- {
- // 产生随机数
- index = (int) (10.0 * rand() / (RAND_MAX + 1.0));
- // printf("index:%d\r" , index); // 往标准输出中打印数据,因此如果没有\n则数据不会立即显示
- // fprintf( stderr , "\rindex:%d" , index );
- fprintf( stderr ,"\r幸运观众:%s" , NameS[index]); // fprintf可以指定输出的目标文件
- /// stderr 则是标准出错文件,文件没有缓冲区 , 只要有数据则立即显示
-
- // 1s = 1000 ms = 1000000us
-
- usleep(300000);
- fprintf( stderr ,"\r " ); // 使用空格当前这一行数据
-
- }
-
- // 每间隔0.5秒出现一个名字
- for (int i = 0; i < 5; i++)
- {
- // 产生随机数
- index = (int) (10.0 * rand() / (RAND_MAX + 1.0));
- // printf("index:%d\r" , index);
- // fprintf( stderr , "\rindex:%d" , index );
- fprintf( stderr ,"\r幸运观众:%s" , NameS[index]);
-
- // 1s = 1000 ms = 1000000us
-
- usleep(500000);
- fprintf( stderr ,"\r " );
- }
-
-
- // 每间隔1秒出现一个名字
- for (int i = 0; i < 3; i++)
- {
- // 产生随机数
- index = (int) (10.0 * rand() / (RAND_MAX + 1.0));
- // printf("index:%d\r" , index);
- // fprintf( stderr , "\rindex:%d" , index );
- fprintf( stderr ,"\r幸运观众:%s" , NameS[index]);
-
- // 1s = 1000 ms = 1000000us
-
- sleep(1);
- fprintf( stderr ,"\r " );
- }
-
- fprintf( stderr ,"\r幸运观众:%s\n" , NameS[index]);
-
-
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- //- 用1种方法表示 a[2][3] 的地址。 : &a[2][3] *(a+2)+3
- printf("&a[2][3]:%p\n" , &a[2][3] );
- printf("*(a+2)+3:%p\n" ,*(a+2)+3 );
-
-
- printf("**************************\n");
-
- //- 用2种完全等价的方法表示 a[2][0] 的地址。
- printf("&a[2][0]:%p\n" , &a[2][0] );
- printf("a[2]:%p\n" , a[2] );
- printf("&a[2]:%p\n" , &a[2] );
- printf("*(a+2):%p\n" ,*(a+2) );
-
- printf("**************************\n");
-
- // - 用3种完全等价的方法表示 a[0][0] 的地址。
- printf("&a[0][0]:%p\n" , &a[0][0] );
- printf("a[0]:%p\n" , a[0] );
- printf("&a[0]:%p\n" , &a[0] );
- printf("*a:%p\n" ,*a );
- printf("a:%p\n" ,a );
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- #include <stdio.h>
- int main(void)
- {
- int a[] = {1, 2, 3, 4};
- int i, *p;
- for(i=0, p=a; i<4; i++, p++)
- {
- printf("%d %d\n", a[i], *p);
- }
- return 0;
- }
-
- 1 1
- 2 2
- 3 3
- 4 4
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- int a[3]; // 此处,a 代表整个数组
- printf("%d\n", sizeof(a)); // 此处,a 代表整个数组,因此计算的结果是整个数组的大小
- printf("%p\n", &a); // 此处,a 代表整个数组,此处为整个数组的地址
-
- int *p = a; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] , 指针p 指向的是数组中第0个元素的地址
- p = a + 1; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] , 指针p 指向的是数组中第1个元素的地址
- function(a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] , 传递给函数function的数组中第0个元素的地址
- scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
C语言只有在第一含义的场合下表现为数组,其他大部分场合都表现为首元素的地址,当数组表现为首元素地址时,实际上就是一个指向其首元素的指针。数组运算实际上就是指针运算。
数组的下标实际上就是基于入口地址的偏移量。
- int arr[10];
- a[3] --》 基于 a 往后面偏移 3个int 的数据
- a[0]
a[i] = 100; 等价于 *(a+i) = 100;
- a[i] = 100;
- *(a+i) = 100;
- *(i+a) = 100;
- i[a] = 100;
- printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
- printf("%p\n", &"abcd"); // 此处 "abcd" 代表整个数组
-
- printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址
- char *p1 = "abcd"; // 此处 "abcd" 代表匿名数组的首元素地址
- char *p2 = "abcd" + 1; // 此处 "abcd" 代表匿名数组的首元素地址
- 概念:长度为0的数组,比如 int data[0];
- 用途:放在结构体的末尾,作为可变长度数据的入口
- 因为数组是唯一一个允许进行越界访问的接口
- 示例:
- struct node
- {
- /* 结构体的其他成员 */
- // 成员1
- // 成员2
- // ... ...
-
- int len;
- char *data[0];
- };
-
- // 给结构体额外分配 10 个字节的内存。
- struct node *p = malloc(sizeof(struct node) + 10);
- p->len = 10;
-
- // 额外分配的内存可以通过 data 来使用
- p->data[0] ~ p->data[9]
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- 概念:定义时,使用变量作为元素个数的数组。
- 要点:变长数组仅仅指元素个数在定义时是变量,而绝非指数组的长度可长可短。实际上,不管是普通数组还是所谓的变长数组,数组一旦定义完毕,其长度则不可改变。
- 只有在定义之前数组的大小是不确定的, 一旦定义结束后数组的大小就固定下来,与变量不在有任何的关系
- 示例:
- int len = 5;
- scanf("%d" , &len) ;//99
- int a[len]; // 数组元素个数 len 是变量,因此数组 a 是变长数组
- len = 99 ; // 不再影响数组的大小
-
- int x = 2;
- int y = 3;
- int b[x][y]; // 数组元素个数 x、y 是变量,因此数组 b 是变长数组
- int b[2][y]; // 数组元素个数 y 是变量,因此数组 b 是变长数组
- int b[x][3]; // 数组元素个数 x 是变量,因此数组 b 是变长数组
- int len = 5;
- int a[len] = {1,2,3,4,5}; // 数组 a 不可初始化
char型指针实质上跟别的类型的指针并无本质区别,但由于C语言中的字符串以字符数组的方式存储,而数组在大多数场合又会表现为指针,因此字符串在绝大多数场合就表现为char型指针。
char *p = "abcd"; // 变量p存放的是匿名数组"abcd" 的入口地址 == ‘a’的地址
- 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);
- int * p ; // *p 把p所指向的内存中的二进制码通过【整型的规则】进行解析的到数据
- char * p1 ; // *p1 把p1所指向的内存中的二进制码通过【字符型的规则】进行解析的到数据
- float * p2 ; // *p2 把p2所指向的内存中的二进制码通过【浮点的规则】进行解析的到数据
- void * p3 ; // *p3 把p3所指向的内存中的二进制码通过 【不知道什么规则】 进行解析的到数据
- // 因此 *p3 是不合理的,没有具体的规则解析内存则无法解析 ---》 无法直接使用 void 指针
- // 指针 p 指向一块 4 字节的内存,且这4字节数据类型未确定
- void *p = malloc(4); // malloc (4) 在堆内存中申请4个字节的内存空间并返回
-
- // 1,将这 4 字节内存用来存储 int 型数据
- *(int *)p = 100; // 先使用(int *) 来强制类型转换把p的类型转为 int *。然后才能*解引用
- printf("%d\n", *(int *)p);
-
- // 2,将这 4 字节内存用来存储 float 型数据
- *(float *)p = 3.14;
- printf("%f\n", *(float *)p);
void *一般用于函数的返回值,比如:
以上函数都是用于在堆中申请内存的操作接口,由于申请到的内存可能会用于存储任何类型的数据,因此在设计这类申请内存的函数的时候不应该明确具体内存的地址类型,否则可能需要设计N个返回不同类型的申请函数。
- 危害:
- 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
- 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
- 产生原因:
- 指针定义之后,未初始化
- 指针所指向的内存,被系统回收
- 指针越界
- 如何防止:
- 指针定义时,及时初始化
- 绝不引用已被系统回收的内存
- 确认所申请的内存边界,谨防越界
很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。
- // 1,刚定义的指针,让其指向零地址以确保安全:
- char *p1 = NULL;
- int *p2 = NULL;
- float *p3 = 0 ;
-
- // 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型数据)
-
- long long a ;
- char * p = &a ;
-
- printf("p:%p\n" , p);
-
- printf("p+1:%p\n" , p+1);
- printf("p+2:%p\n" , p+2);
-
- printf("p-1:%p\n" , p-1);
- printf("p-2:%p\n" , p-2);
- 指针的加减运算时 它加减的目标 大小是以指针的类型为单位,
- 如果指针指向的是一个整形地址则+1、-1的时候以 int 为单位进行加减,
- 如果指针的类型是Long 类型则 则+1、-1的时候以 long 为单位进行加减
- 指针的加减与指针所指向的数据本身的类型没有任何关系(比如以上代码a不管是什么类型都不影响p的运算)
拓展:
- long long ** p1 = &a ;
- int ** p2 ;
- char ** p3 ;
以上代码中p是一个二级指针,因此可以理解为: *p 明确了变量p是一个指针,剩下的 long long * 表示该变量存储的类型是一个 long long类型的地址, 由于地址的大小在某一个系统中是固定的,随意这个指针p在进行加减运算的时候步伐与地址的大小相关。
以上 p1 \p2 \p3 加减时都是以8字节(64为系统 ) 、 4 字节 (32为系统)为单位。
- int a = 100;
- int b = 200;
-
- // 第1中形式,const修饰p1本身,导致p1本身无法修改
- int * const p1 = &a; // p1 的指向无法被修改它将“永远”指向 a 的地址
-
- // 【拓展】 从侧面修改p1的指向
- int ** p4 = &p1 ;
- *p4 = &b ; // 可以通过二级指针来间接修改p3所指向的内容
- printf("*p3: %d \n" , *p3);
-
- // 第2中形式,const修饰p2的目标,导致无法通过p2修改a
- int const *p2 = &a; // 无法通过 p2 来修改 变量 a 的数据
- const int *p2 = &a;
常目标常指针
- const int * const ptr = &a ;
- ptr = &b ; // [不允许]
- *ptr = 777 ; // [不允许]
返回值类型 (*p) (参数列表) ;
- void f (int) // 函数 f 的类型是: void (int)
- {
-
- }
-
-
- void (*p)(int); // 指针 p 专门用于指向类型为 void (int) 的函数
-
- p = &f; // p 指向 f(取址符&可以省略)
- p = f; // p 指向 f
-
- // 以下三个式子是等价的:
- f (666); // 直接调用函数 f
- (*p)(666); // 通过索引指针 p 的目标,间接调用函数 f
- p (666); // 函数指针在索引其目标时,星号可以省略
注意:
本文细讲了打怪路上的数组和指针的特点和消灭方法,各位只需认真学习,即可消灭它们。祝各位都可爬上C语巅峰,斩尽拦路小妖。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。