<<C语言深度剖析>>学习笔记之五:指针与数组
来源:互联网 发布:广州尔玛网络 编辑:程序博客网 时间:2024/05/21 12:41
1.指针:
通俗说法:
一个基本的数据类型(如char、int、struct)后面加上"*"号就构成了一个指针类型的模子.这个模子是一定的,与"*"号前面的
数据类型无关--在32位系统下是4Byte.也可以理解成PC指针的寻址能力,比如一个32位的SOC,其寻址能力为4G(2^32).如果
不是4Byte(32bit),又怎么可以完全寻址呢?"*"前面的数据类型影响指针的步伐,比如指针加1减1其跨越内存的幅度是以这个
数据类型为单位的.
1-1.将指定值写入到指定内存地址.
指针可以理解成和int一样是一种"类型".我们在编程中常常会用到"强制类型转换".比如把一个int型强制转换为char型,如下:
char cTestVal = (char)iTestVal;
因此,我们把一个数值也可以强制转换为地址.如:
0x12ff7c是一个数据.变为一个地址如下:
(int *)0x12ff7c.
往内存0x12ff7c地址上写入数值0x100.实现代码如下:
int *p = (int *)0x12ff7c;
*p = 0x100;
也可以直接写成这样:
*(int *)0x12ff7c = 0x100;
2.数组
2-1.a,&a[0]和&a区别:
&a[0]:数组首元素的地址;
&a:数组的首地址.
a和&a[0]是一样的.
二者虽然一样,但是他们的步进值是不一样的.比如&a[0]+1和&a+1.
示例如下:
#include <stdio.h>int main(int argc,char **argv){ int a[5]={1,2,3,4,5}; int *ptr1=(int *)(&a+1); printf("%x,%x,%x,%x\n",*(ptr1 - 1),*(a + 1),*(int *)((int)a + 1),*(&a[0] + 1)); return 0;}
输出结果:
root@seven-laptop:~/learn/C_Program# ./array 5,2,2000000,2root@seven-laptop:~/learn/C_Program#对输出结果简析:
&a表示整个数组的起始地址,&a+1表示步进了sizeof(a)的地址空间大小.因此,此时ptrl是指向了数组a末尾再下一个元素的位置.ptrl - 1,指针步进是以其指向类型
为步进值的.ptrl指向int类型.因此ptrl-1表示ptrl再往回退4个byte,落在a的末端最后一个元素位置,输出5.
a和&a[0]都表示数组首元素的地址,a+1是指偏离首元素地址的下一个元素;
*(int *)((int)a + 1):(int)a + 1意思是指元素a[0]的第二个字节.由于是%x格式输出,即从这个地址开始后连续4个字节.下面画出数据在内存存放的示意图:
大端模式:
010002小端模式:100
02000
[注]:上述两个表左边表示数组起始位置,地址从左往右越来越高.
小端模式:一个字的高地址存放一个字的高字节.因此,"0 0 0 2"按小端模式规则存放为"2 0 0 0".输出为0x2000000;
大端模式:一个字的高地址存放一个字的低字节.因此,"0 0 1 0"按大端模式规则存放为"0 0 1 0".输出为0x100.
2-2.数组名作为右值:
当数组名作为右值时,其意义和&a[0]是一样的,代表的是数组首元素的首地址,而不是数组的首地址.如:
int array[10];
int *p = array;
2-3.数组名不能作为左值:
3.指针与数组的关系:
3-1.相同点
以下标的形式访问没有本质的区别.如下面这个示例:
#include <stdio.h>#include <stdlib.h>#include <string.h>int main(int argc,char **argv){ int lenp = 0,lena = 0,i = 0; char *pCh = "0123456789"; char array[] = "0123456789"; lenp = strlen(pCh); lena = sizeof(array); printf("lenp = %d,lena = %d\n",lenp,lena); for(i = 0; i < lenp; ++i) { printf("pCh[%d] = %c,array[%d] = %c\n",i,pCh[i],i,array[i]); } return 0;}输出结果:
root@seven-laptop:~/learn/C_Program# ./point lenp = 10,lena = 11pCh[0] = 0,array[0] = 0pCh[1] = 1,array[1] = 1pCh[2] = 2,array[2] = 2pCh[3] = 3,array[3] = 3pCh[4] = 4,array[4] = 4pCh[5] = 5,array[5] = 5pCh[6] = 6,array[6] = 6pCh[7] = 7,array[7] = 7pCh[8] = 8,array[8] = 8pCh[9] = 9,array[9] = 9root@seven-laptop:~/learn/C_Program# vim point.c
3-2.不同点
3-2-1:大小
指针:在32位系统下,永远只占4个Byte,可以指向任何地方,但是并不是每个地方都可以通过这个指针访问的;
数组:大小与元素的类型和个数有关,可以存放任何类型的数据,但是不能存放函数.
3-2-2:数据的访问方式
指针:间接访问,首先取得指针变量p的内容,把它当作地址,然后从这个地址提取数据或向这个地址写入数据;
数组:直接访问数组,只能通过"基地址+偏移量"来实现访问.
3-2-3:处理对象
指针:动态数据;
数组:存储固定数目且数据类型相同的元素.
4.指针数组和数组指针
指针数组:首先它是一个数组,数组的元素都是指针;
数组指针:首先它是一个指针,它指向一个数组.
下面示例对比两者的区别:
int *p1[10];指针数组,"[]"的优先级比“*”要高.因此p1先跟"[]"结合,形成数组,数组名为p1.数组元素类型为int型指针.int (*p2)[10];数组指针,p2是一个指针,它指向一个包含10个int类型数据的数组.注意这个数组是匿名的.
下面给出一个示例显示数组指针的访问数组元素的方法:
#include <stdlib.h>#include <stdio.h>#include <string.h>int main(int argc,char **argv){ int array_a[5] = {0,1,2,3,4}; int(*pa)[5]; pa = &array_a; printf("pa[0][0] = %d\n",pa[0][0]); printf("pa[0][1] = %d\n",pa[0][1]); printf("pa[0][2] = %d\n",pa[0][2]); printf("pa[0][3] = %d\n",pa[0][3]); printf("pa[0][4] = %d\n",pa[0][4]); printf("Address of pa = %p\n",pa); printf("Address of (pa + 1) = %p\n",(pa + 1)); return 0;}输出结果:
pa[0][0] = 0pa[0][1] = 1pa[0][2] = 2pa[0][3] = 3pa[0][4] = 4Address of pa = 0xbfd6856cAddress of (pa + 1) = 0xbfd68580
二维数组可以理解为一维数组,只不过这个"一维数组"里面的元素也是一个数组.
5-1.二维数组元素的访问方式:
1).a[i][j];
2).*(*(a + i) + j)
*(*(a + i) + j)可以拆开下面几步理解:
1).a:和一维数组一样,是这个数组的首元素的首地址;
2).a + i:和一维数组一样,表示步进"i"个数组元素,只不过这里步进的元素是"数组",因此,a + i表示第i-1个元素的地址.因为这个元素是数组.因此,a + i表示的意义变成了这个一维数组整个数组的首地址.因此,(a + i)和*(a + i)的地址值是一样的.这就相当于一维数组里面a和&a[0]的区别.只不过一个是数组的起始地址,一个是数组第一个元素的起始地址;
3).虽然(a + i)和*(a + i)的地址值是一样的,但是它们有着本质的区别--它们的步进值是不一样的.这就相当于&a和&a[0]二者的区别.我们现在要找的是元素.因此必须以&a[0]的方式步进--即*(a + i);
4).*(a + i) + j:确定我们要找的元素的内存地址;
5).*(*(a + i) + j):把我们要找的元素的内存地址上的数值给读出来.
示例:
#include <stdio.h>int main(int argc,char **argv){ int array[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}}; printf("Size Of Two_Array Is %d\n",sizeof(array)); printf("Size Of Two_Array[0] Is %d\n",sizeof(array[0])); printf("Size Of Two_Array[1] Is %d\n",sizeof(array[1])); printf("Size Of Two_Array[2] Is %d\n",sizeof(array[2])); printf("Address of array[1][2] = %p,Address of (*(array + 1) + 2) = %p\n",&array[1][2],*(array + 1) + 2); printf("Address of array[0] = %p,address of (array[0] + 1) = %p\n",array[0],(array[0 + 1])); printf("array = %p,(array + 1) = %p,*(array + 1) = %p,**(array + 1) = %d\n",array,(array + 1),*(array + 1),**(array + 1)); printf("array[1][2] = %d\n",array[1][2]); printf("*((*(array + 1)) + 2) = %d\n",*((*(array + 1)) + 2)); return 0;}输出结果:
root@seven-laptop:~/learn/C_Program# ./twoarray Size Of Two_Array Is 48Size Of Two_Array[0] Is 16Size Of Two_Array[1] Is 16Size Of Two_Array[2] Is 16Address of array[1][2] = 0xbfd4bd4c,Address of (*(array + 1) + 2) = 0xbfd4bd4cAddress of array[0] = 0xbfd4bd34,address of (array[0] + 1) = 0xbfd4bd44array = 0xbfd4bd34,(array + 1) = 0xbfd4bd44,*(array + 1) = 0xbfd4bd44,**(array + 1) = 4array[1][2] = 6*((*(array + 1)) + 2) = 6root@seven-laptop:~/learn/C_Program#
5-2.二维数组初始化误区:
正确写法:
int array[2][3] = {{1,2,3},{4,5,6}};
手误写法:
int array[2][3] = {(1,2,3),(4,5,6)};
6.二级指针
二级指针是指其指向的对象不是数据,而是一级指针的地址.
7.数组参数与指针参数
7-1.一维数组参数
一维数组做参数时,编译器总是把它解析成一个指向其首元素首地址的指针.下面给出一个示例:
#include <stdio.h>void func_array(int a[5]){ int *p = a; printf("size of a = %d\n",sizeof(a)); printf("a[3] = %d\n",a[3]); printf("p + 3 = %d\n",*(p + 3)); *(p + 3) = 8;}int main(int argc,char **argv){ int array[5] = {1,2,3,4,5}; func_array(array); printf("array[3] = %d\n",array[3]); return 0;}输出结果:
root@seven-laptop:~/learn/C_Program# ./parameter size of a = 4a[3] = 4p + 3 = 4array[3] = 8root@seven-laptop:~/learn/C_Program# vim parameter.c由于一维数组作参数时,编译器只是把这个数组的首地址传进去.因此,上述函数func_array(int a[5])把参数中的"5"去掉也是可以的.这样至少不会让人误会成只能传递一个5个元素的数组.
7-2.一级指针参数
传参数的时候如果涉及到传入参数的改变的话,一定要注意编译器有个"拷贝副本"的动作.
如下,我们写一个函数来分配内存.下面的代码是有问题的:
#include <stdio.h>#include <stdlib.h>#include <string.h>void get_mem(char *p,int num){ p = (char *)malloc(num * sizeof(char));}int main(int argc,char **argv){ char *str = NULL; get_mem(str,10); strcpy(str,"hello"); printf("str = %s\n",str); free(str); return 0;}运行直接报段错误.
代码修改一:
#include <stdio.h>#include <stdlib.h>#include <string.h>void get_mem(char **p,int num){ *p = (char *)malloc(num * sizeof(char));}int main(int argc,char **argv){ char *str = NULL; get_mem(&str,10); strcpy(str,"hello"); printf("str = %s\n",str); free(str); return 0;}
代码修改二:(这种方式更容易理解)
#include <stdio.h>#include <stdlib.h>#include <string.h>char *get_mem(char *p,int num){ p = (char *)malloc(num * sizeof(char));}int main(int argc,char **argv){ char *str = NULL; str = get_mem(str,10); strcpy(str,"hello"); printf("str = %s\n",str); free(str); return 0;}
8.函数指针
函数指针,是一个指针,指向了函数.如下:
char * (*fun1)(char *p1,char *p2);
fun1是一个指针,它指向的对象是一个函数,这个函数的返回值是char 指针,有两个参数,均为char指针.
8-1.函数指针的定义:
char * (*fun1)(char *p1,char *p2)
8-2.函数指针的使用:
如同使用指针指向的变量一样,我们可以类比下面指针和函数指针:
提取char型指针里面的内容:
char i = *p;
运行函数指针指向的函数:
(*fun1)("aa","bb");
[注:](*fun1)("aa","bb");可以按照下面理解:
fun1为某个函数的地址,*fun1把这个函数给拿出来.其实和我们常用的函数调用是一样的:func();
*fun1对应了这个函数名(函数名就是函数的地址标志),可以理解成一种替换:用*fun1替换func.
示例一:
#include <stdlib.h>#include <stdio.h>#include <string.h>char *func1(char *p1,char *p2){ int i = -1; i = strcmp(p1,p2); if(0 == i) { printf("%s\n",p1); } else { printf("%s\n",p2); }}int main(int argc,char **argv){ char *(*pf)(char *,char *); pf = &func1; *pf("aa","bb"); return 0;}[注:]为了突现函数指针,上述代码中
*pf("aa","bb");最好改写成:
(*pf)("aa","bb");
示例二:
写一程序让其从0地址开始运行.
1).将0地址强制转换为函数指针:
(void(*)())0
2).函数指针所指向的内容和我们常用的函数调用只是一种替换关系(分析见上述):
(*(void(*)())0)();
9.函数指针数组
函数指针数组,分开理解:首先它是一个数组;其次,数组里面的元素有是类型的,那么元素类型就是函数指针.如:
char *(*pf[3])(char *p);
分析:
1).*pf[3],"[]"的优先级比"*"高,因此,它是一个数组,数组名是pf,里面有3个元素;
2).去掉数组名、"[]"、元素个数"3",剩下的就元素类型了.如下:
char *(*)(char *p),这是一个函数指针,指向的函数类型为:返回值为char *,只有一个参数,是char *.
下面给出一个示例:
#include <stdio.h>#include <stdlib.h>char *func1(char *p){ printf("%s\n",p); return p;}char *func2(char *p){ printf("%s\n",p); return p;}char *func3(char *p){ printf("%s\n",p); return p;}int main(int argc,char **argv){ char *(*pf[3])(char *); pf[0] = func1; pf[1] = func2; pf[2] = func3; pf[0]("func1"); pf[1]("func2"); pf[2]("func3"); return 0;}输出结果:
root@seven-laptop:~/learn/C_Program# ./pffunc1func2func3root@seven-laptop:~/learn/C_Program# vim pf.c总结分析:
函数指针:
1).首先它是一个指针,而指针指向是有类型的,比如int;
2).其次这个指针安放的内容是这个函数的地址.我们提取一个普通的指针只要在指针前面加"*",函数指针其实也一样,只不过为了突出它是函数指针,用"()"括起来.如下:
普通指针:
int *p;
int iTestVal = *p;
函数指针:
int *(*pf)(int i);
int *func(int i);
pf = &func; //这里func和&func实际效果是一样的,只不过本质是完全不同而已.类似一维数组&a和&a[0].
(*pf)(5); //实际使用函数指针只需要把函数指针的内容提取出来(*pf)替换函数名我们常规调用方法一样.这里只是用(*pf)替换func.
函数指针数组:
1).首先它是一个数组,数组是有数组名、元素个数、元素类型.因此,我们定义一个函数指针数组可以按下面的流程走:
确定数组名及元素个数-->确定数组里面的元素类型.如下:
pf[3] -- > char *(*pf[3])(char *)
2).函数指针数组里面的元素都是函数指针,如pf[0]、pf[1]、pf[2]类型都是char *(*)(char *);
3).只要函数形式是:返回值为char *、参数只有一个而且是char *的都可以被安放在pf[0]、pf[1]、pf[2];
4).实际使用过程的时候,和常规调用方式一样,只是把函数指针安放的内容去替换函数名.
10.函数指针数组的指针
char *(*(*pf)[3])(char *);
上面就是一个函数指针数组的指针.下面我们分步来理解:
1).首先它是一个指针:(*pf);
2).既然是一个指针,它总会有指向对象.从大体上看这个指针是指向一个数组(*pf)[3].这个数组有三个元素,每个元素的类型是char *(*)(char *);
函数指针数组的指针, 难理解,很少用.不说了.买了个表.
- <<C语言深度剖析>>学习笔记之五:指针与数组
- 《C语言深度剖析》笔记 之 指针与数组
- C语言深度剖析学习笔记-指针、数组、内存、函数
- C语言深度剖析笔记(指针和数组)
- C语言深度剖析-----数组与指针分析
- C语言深度剖析之—指针与内存地址(函数指针,普通指针,指针数组,数组的指针,指针的指针)
- C语言深度剖析之—指针与内存地址(函数指针,普通指针,指针数组,数组的指针,指针的指针)
- C语言深度剖析之—指针与内存地址(函数指针,普通指针,指针数组,数组的指针,指针的指针)
- <<C语言深度剖析>>学习笔记之二:关键字详解
- <<C语言深度剖析>>学习笔记之三:符号
- c语言深度剖析之学习笔记(二)------------符号
- 【C】【笔记】《C语言深度剖析》第四章 指针和数组
- 《C语言深度剖析》学习笔记1
- 《c语言深度剖析》学习笔记2
- 《c语言深度剖析》学习笔记3
- 《c语言深度剖析》学习笔记4
- C语言深度剖析学习笔记
- C语言深度剖析学习笔记-符号
- 基于Predictive Parsing的ABNF语法分析器(十四)——RFC2234文法解析实战
- WPF(2)----背景图片设置
- android录音功能的实现
- redhat5.5 如何yum本地包
- js学习累积
- <<C语言深度剖析>>学习笔记之五:指针与数组
- 免插件打造wordpress投稿页面
- UIApplication sharedApplication详细解释-IOS
- jquery基础总结
- JAVA移位运算符
- MySQL数据库my.cnf配置文件注释详解
- oracle建主键
- nyoj 412Same binary weight
- ora-00988