1,问题的引入 int a;//定义了一个变量a,类型为int,实质是分配了一个4字节内存单元 a = 100;//把数值100存放到变量a对应的存储单元中去 b = a;//取出变量a的值,然后存放到变量b对应的存储单元中去。 ===> 在C语言中,任何一个变量名,都有两层含义: (1)代表该变量的存储单元 左值(lvalue) (2)代表该变量的值 右值(rvalue) 而且,我们对变量的访问只有两种情况: (1)把一个值存到变量的存储空间中去 (write) (2)从变量的存储空间中取值 (read) 我们知道系统已经把变量名与变量的地址相关联,系统实质上是通过地址来 访问变量的。于是乎,有人提问是不是我们也可以直接通过地址来访问变量呢? ===> 指针
2,指针的概念 地址: 系统把内存以一个字节为单位划分成许多份进行编号,这个编号就是 内存单元中的地址。 在C语言中,指针的概念与地址差不多,你可以认为指针就是有类型的地址。 一个变量的首地址,称为该变量的"指针"。他标志着该变量的内容从哪里开始。
3,指针变量 ★指针变量也是一个变量,它是用来保存一个对象的地址的。 指针变量的定义: 类型 指针变量名; “类型”:指针变量指向的类型 “指向”: 如果我保存了你的地址,那么就说我指向你 eg: int a; int p;//定义了一个指针变量p,它的类型是: int p = &a;//p是一个指针变量名,他指向的类型是int 注意:在32位的处理器里,地址都是32位的,即指针变量分配的空间都是4个字节, 所以把指针变量强制转化为其它类型的指针类型也不会失真。 因此 void 也叫通用指针。 ★指针变量的类型决定了该指针变量与整型数据之间的运算,使其实际地址变化之间 的倍率关系。
eg: int a;
char b;
int *p1 = &a;
char *p2 = &b;
printf("%p\n",p1);
printf("%p\n",p1+1);
printf("%p\n",p2);
printf("%p\n",p2+1);
4,如何获取地址 (1)通过取地址符 & &对象名: 表示取该对象的地址 对象: 变量、数组元素。。。 (2)有些对象的名字本身就表示地址 如: 数组名、函数名。。。 注意: 这两种方式获取到的地址都是有类型的。
5,如何访问指针指向的空间 : *(指向运算符) (地址) <==> 地址对应的那个变量 即(地址)也可作为左值和右值,还可以取地址 *&a <==> a *& 可直接约掉
注意: 要与乘运算符和定义时的*相区别
int *p,a=3;
p = &a;
printf("%d\n",(*p)*2);
6, 数组与指针 数组元素与普通变量一样,也有自己的地址。 并且数组元素间的地址是连续的,且数组名就是数组的首地址。 eg: int a[10]; int *p; p = &a[0]; 把100赋值为a[0],请问有几种写法 a[0] = 100; *p = 100; *a = 100;//数组名就是数组的首地址(首个元素的地址) 那么 *(a+1) ==> a[1]
★ *(p+i) <=> p[i],when i>=0
7, 二维数组与指针 int a[3][4]; 二维数组a可以看成元素为int[4]的一维数组, 所以,*(a+i)<=>a[i]指向的是该一维数组的第i个元素,该元素为int[4]类型。 因此,这里需要再指向一次才能指向二维数组的元素: ((a+i)+j) <==> a[i][j],这时元素类型为int型。
表达式(i>=0,j>=0) 类型/sizeof 含义 值
a+i int[4]*/int[3][4](没有i) 指向第i行的首地址 &a[i]
*(a+i)+j<=>a[i]+j int*/ int[4](没有j) 第i行第j列的元素的地址 &a[i][j]
*(*(a+i)+j)<=>a[i][j] int 第i行第j列的元素 a[i][j]
eg:
char a[3]={'a','b','c'};
char b[2][3]={{'1','2','3'},{'4','5','\0'}};
printf("%ld %ld %c\n",sizeof(a),sizeof(a+1),*(a+1));
printf("%ld %ld %ld %s\n",sizeof(b),sizeof(b+1),sizeof(*(b+1)),*(b+1));
8, 字符串与指针 在C语言中,并没有内置字符串的类型,C语言的字符串是通过char*指针来实现的。
char *str = "ABCDEF";//把保存在rodata段里的字符串的首地址赋值为str
char str1[]="ABCDEF";//定义一个字符数组,并把它的内容初始化为"ABCDEF"
str: 保存字符串的首地址,即字符'A'的地址。
str+1: 指向字符'B'的地址。
*(str+1) = '2';//error,因为这里没有定义字符数组,没有分配空间,
//常量保存在rodata段内,不能被改变
*(str1+1)= '2';//right
"ABCDEF"在C语言中,把该字符串的首地址赋值给了str,由于字符串肯定会有终止符,
系统可以把字符串的内容完整的访问。
9, NULL指针与野指针
(1)NULL也叫空指针,其实是系统定义第一个宏,表示不指向任何空间。
#define NULL ((void*)0)
所以NULL在逻辑运算中表示假
NULL不能被访问,否则就会报段错误。
(2)野指针
野指针不是NULL指针,而是指向非法分配空间的指针
野指针的成因有两种
1)指针变量未初始化
2)指针变量被free或delete后没有置为NULL,让人误以为p还是个合法的指针
eg:
(1) void swap(int *a,int *b)
{
int *p;
*p = *a;
*a = *b;
*b = *p;
}
(2)
char *p = (char*)malloc(100);
strcpy(p,"hello");
free(p);
....
....
if( p!= NULL)
{
strcpy(p,"word");
}
11, 数组指针与指针数组 int a[10];//定义了一个有10个元素的数组a,每个元素都是int型 如果我们要定义一个指针p,来保存a的地址。 该如何定义?
typeof(a) *p;
==> int[10] *p;
==> int (*p)[10];
p就是一个指针,指向一个数组(int[10]),那么我们把p叫做数组指针
因为'*'的结合性较低,所以这里要把(*p),表示定义的是指针。
★二维数组名就是一个数组指针,所以数组指针的用法就是二维数组名的用法。
int *p[10];
这里的p是一个数组,里面有10个元素,每个元素都是int *类型,
那么我们把p叫做指针数组。
eg: int (*p1)[10];//定义了一个数组指针变量
int *p2[10];//定义了一个指针数组
printf("%d %d\n",sizeof(p1),sizeof(p2));//8 80
12, main的参数 在unix/linux下面,main函数的原型,应该是如下的: int main(int argc, char *argv[]) { } 在操作系统调用你的可执行程序时(调用你的main函数),允许带参数, 只不过这些参数都是字符串类型的(char *) argc: argument count表示运行你的程序时,参数的个数(程序名,也是一个参数) argv: argument vector 参数字符串指针的数组 eg: ./a.out 1 2
13, 二级指针与多级指针 其实main函数原型也可写成如下模式 int main(int argc, char **argv) {}
可见指针数组就是个二级指针
二级指针就是能指向两次的指针,一般有以下两种理解方式
(1) 指针变量的地址
eg: int a=100;
int *p = &a;
int **p2 = &p;//p2就是一个二级指针变量,它的类型是int **
printf("%d %d %d\n",a,*p,**p2);
(2)指针数组
如果一个数组的元素是指针,则该数组名就是二级指针。
同理,如果一个数组的元素是二级指针,则该数组名就是三级指针。
....
在C语言中,char *可以表示字符串。其实指针也可以表示一个已定义了的数组。
它们的区别是字符串有终止符'\0'来确定长度,而数组必须要我们自己给定长度。
14, 函数指针 函数指针变量就是用来保存函数地址的变量。 函数指针变量的定义: 指向的函数返回值类型 (*函数指针变量名)(指向的函数形参列表);
通过函数指针调用指向的函数:
(*函数指针)(实参列表)
或
(函数指针)(实参列表)
函数指针的用法:
1)线程池
2)回调函数
回调函数就是一个被作为参数传递的函数。
用户把需要调用的函数的指针作为参数传递给一个函数,以便该函数在处理相应事件的时候可以灵活的使用不同
的方法。
应用示例:一般操作系统都实现了中断机制,但用户不能直接定义中断处理函数,只能在中断处理函数中再调用
用户自定义的回调函数。
☆函数指针数组
void (*a[3])(int,int);//定义了一个函数指针数组a,他有三个元素,每个元素都是函数指针
//返回值为void,参数类型为(int,int)的函数指针。
15, 指针作为函数参数 由于形参不能改变实参的值,被调函数想传递数据给主调函数一般用return返回。 除此之外还有两种方式: (1)访问全局变量 (2)带指针作为函数参数
16, 动态内存分配 malloc free realloc calloc
NAME malloc, free, calloc, realloc - allocate and free dynamic memory
SYNOPSIS #include <stdlib.h>
malloc: memory allocate,用来在进程的动态内存区域(堆)分配一块内存,
并且把该分配的内存的首地址作为函数的返回值返回。
"进程的动态内存区域":也是一个内存区域,只不过该区域的内存的生存期
是随进程持续性。一旦分配,如果不手动释放(free),那么就一直存在,直到进程退出。
void *malloc(size_t size);
@size: 你要分配的动态内存的大小,单位为字节
返回值:
如果成功,将返回分配空间的首地址
如果失败,返回NULL
free: 用来释放malloc/calloc申请的动态内存区域
void free(void *ptr);
@ptr: 你要释放那段内存的首地址(malloc/calloc的返回值)
calloc用来分配一段内存空间用来存放数组
calloc(n,size) <==> malloc(n*size);
void *calloc(size_t nmemb, size_t size);
@nmemb : 数组元素的个数
@size : 每个元素占多少个字节
返回值:
如果成功,将返回分配空间的首地址
如果失败,返回NULL
realloc: repeat allocate
1) 改变ptr指向的那段动态内存的大小
ptr指向的原来那段内存空间的内容不变,增加和减少的内存都不会初始化。
2) 如果ptr == NULL
那么realloc(NULL,size) <==> malloc(size)
3) 如果ptr!=NULL,且size==0
那么realloc(ptr,0) <==> free(ptr)
void *realloc(void *ptr, size_t size);
进程内存空间布局
|