C语言指针
ChampLixxx 人气:0在C语言中,任何一个变量,都有两层含义:
(1) 代表该变量的存储单元的地址;变量的地址 左值 lvalue
(2) 代表该变量的值;右值 rvalue
对于一个变量的访问,只有两种情况:
一是把一个值写到变量的地址中去 (lvalue)
二是从变量的地址中取变量的值 (rvalue)
对象的访问方式
直接访问 : 通过对象名去访问
如:
int a; a = 1024; b = a;
注:直接访问 受到作用域的限制
间接访问 :
通过对象的地址去访问,指针访问。
只要你知道了对象的地址,就可以在任何地方去访问它。
不受作用域的限制。
什么是指针
存储单元(如: memory 内存)的地址:
分配给每个对象的内存单元都有一个编号,这个编号就是我们所说
存储单元的地址。并且存储单元按照字节来编号。
在C语言中,指针的概念与地址差不多的,你可以直接认为指针就是一个地址。
一个变量的地址,我们也称为变量的“指针”
& 取地执符
单目运算符, “取xxx对象的地址”
通过一个对象的指针去访问它,首先要解决对象的指针(地址) 的保存问题。
需要定义另外的一个变量去保存它的地址,这种变量我们称为指针变量。
指针变量
指针变量也是一个变量,也是用来保存数据,只不过指针变量保存的数据是其他对象的地址。
指针变量的定义:
指向的对象的类型 * 指针变量名;
“指向的对象的类型” :
指针变量指向的对象的类型,而不是指针的类型。
如:
int *p; int a=10; p=&a;//p保存了对象a的地址 // p 指向 a
与指针相关的运算符
& : 取地址符
int a;
&a : 表示取对象a的地址
对象a的地址,其实就是对象a的指针。
* : 指向运算符 ,单目运算符
*地址(*指针): 地址对应的那个变量(对象)。
如
int a=10; int *p=&a; int b=*&a;//b=10,int b=a
所以*(&a) <=> a
*& 可以直接约掉 (编译原理状态机的词法分析)
指针变量作为函数参数
传递的还是值!! 只不过这个“实参的值”可能是某个对象的地址!
C语言中,函数参数的传递只能是"值传递"
形参 = 实参的值
野指针:
void XXX2(int * m, int * n) { int * t;//定义了一个指针变量t, 但是t没有被赋值 //t没有赋值,但是不代表它没有值,相反它会有一个相对应的 //不知道这个值是多少 undefine *t = *m; //操作野指针,可能导致 段错误(对一个未知的空间进行写操作) *m = *n; *n = *t; //操作野指针,可能导致 段错误(对一个未知的空间进行读操作)
*t 放置到 ‘=’ 符号的左边,代表t指向对象的左值
对t的指向的对象进行写的操作
*t 放置到 ‘=’ 符号的右边,代表t指向对象的右值
对t的指向的对象进行read的操作
所以t是一个“野指针”,我们也不知道 t指向的内存空间是否可读可写,
如果不可读或者不可写,那么后续对*t操作,就会导致内存的非法访问 => 段错误!!
空指针:
NULL 0
在计算机中地址为0的存储空间是不存在的
如果一个指针的值,指向空(NULL)的指针,称之为空指针。
int *p=NULL; *p=1024;//操作空指针 int b=*p;//操作空指针
我们去使用空指针,一定会 造成内存的非法访问 => 段错误
数组与指针
数组元素与普通变量是一样的,数组元素也有自己的地址。
数组元素也有左值和右值,并且数组元素间的地址是相邻的。
数组名可以代表首元素的地址。
a => &a[0], 数组名a当做指针来看
如
int a[]={1,2,3}; int *p=a;//a是数组名当指针来看,==&a[0]
指针作加减的问题:
能不能通过指针p去访问a[1]?
p=p+1;//&a[0]+1==&a[1]
p + i (p是一个指针,i是一个整数值)
注:不是简单地加减数值,而是加减i个指针指向单元的长度。
p + 1 => 往后面挪了一个int单元的长度 ,&a[1]
int a[10]; int * p = a; // p = &a[0] a[1] <=> *&a[1] <=> *(&a[0] + 1) <=> *(a + 1) <=> a[1] a[1] <=> *&a[1] <=> *(&a[0] + 1) <=> *(p + 1) <=> p[1]
数组名a,在代码中有两层含义:
int a[10];
(1) 数组名代表整个数组
&a : 取整个数组a的地址。
&a + 1 : 往后面挪了1个(int[10])单元长度。
(2) 在合适情况下面,数组名可以当作指针来看
a <=> &a[0]
a + 1 : 当作指针来看
=> &a[0] + 1
=> &a[1]
多维数组与指针
在C语言中,所有的数组其实都是一维数组!
int a[3][4]; //int[4] a[3];
表达式 表达式的含义 表达式的值
a 数组名:
(1) 当作指针 &a[0] 取整个一维数组a[0]的地址
(2) 当作整个数组来看
a[0] 数组名:
(1) 当作指针 &a[0][0] 取数组元素a[0][0]的地址
(2) 当作整个一维数组a[0]来看
a[0][0] 数组元素a[0][0] a[0][0] (左值/右值)
a + 1 a是数组名,在此处当作指针 ==&a[0]+1==&a[1]
a[0] + 1 a[0] 是数组名,在此处当作指针 ==&a[0][0]+1==&a[0][1]
a[0][0] + 1 a[0][0]是二维数组a中的一个int类型的元素 ==a[0][0]+1
a[1] + 2 a[1]是数组名,此处当作指针 ==&a[1][0]+2==&a[1][2]
*(a + 1) + 2 a是数组名,在此处当作指针 ==*(&a[0]+1)+2==*(&a[1])+2==a[1]+2==&a[1][2]
指针常量 和 常量指针
他们都是指针,只不过他们之间的属性有一点区别。
指针常量:
指针本身不能改变(指针的指向不能变),但是
它所指向的内存空间里面的内容是可以改变的。
最具有代表性例子,数组名!
定义方式:
指向对象的类型 * const 指针变量名;
int a , b; int * const p = &a; p = &b ; //error
常量指针:
是一个指向常量的地址。指针指向的对象是常量,
那么指针本身的指向是可以改变的,但是这个指针指向
内存空间中的内容是不能够改变。
最具有代表性例子,字符串
char * p = "abcde"; //"abcde" <=> &'a'
定义方式:
const 类型 * 变量名;
or
类型 const * 变量名;
指针数组 与 数组指针
(1) 指针数组
指针数组是一个数组,只不过它里面的每一个元素都是指针罢了!
定义数组:
数组元素类型 数组名[元素个数];
“数组元素类型” : C语言中任意合法的类型都可以。
int * p[4]; //定义了一个指针数组,数组名为p,里面含有4个int*类型的指针。
(2) 数组指针
数组指针是一个指针,只不过这个指针是用来指向一个数组罢了!!
int (*p)[4] ; //定义了一个数组指针,指针变量名为p,用来指向一个含有4个int类型元素的数组的。
如
int a[3][4]; int (*p)[4]; p = a; //&a[0] *(*p + 1) // *(a[0]+1)==*(&a[0][1])==a[0][1] *(*(p + 2)) // *(*(&a[2]))==*(&a[2][0])==a[2][0] *(*(p+3) + 3)// *(*(&a[0]+3)+3)==*(*(&a[3])+3)==*(&a[3][0]+3)==a[3][3]
字符串与指针
字符串就是一串字符。在C语言中,没有字符串这个类型。
C语言字符串是通过 char *(字符指针)来实现。
C语言中的字符串,是用""引起来的一串字符来表示,并且字符串后面默认会加一个\0,\0(ASCII为0的那个字符)字符串结束的标志。
如
"abc" //字符串
"\n" //字符串
"" //字符串 => 空串
'\123' //字符
'\n' //字符
注:
我们只需要保存字符串的首字符地址就可以了,从首字符地址开始找到第一个\0,前面的这些字符就是字符串里面的字符。
在C语言中的字符串(如: “ssssssabcd”)是保存在一个叫做 .rodata(只读数据)的内存区域,字符串代表是这个内存空间的首地址。
"ssssssabcd" => 表达式的值 => &'s' char *p="12345" ==&'1' int *p=&'1'; p+1=&'2';
字符数组:
char s[5] = {'a', 'b', 'c', 'd', 'e'}; //字符数组,与普通数组是一样的,保存在一个.data/栈空间 //数组区域是可读可写的 sizeof(s) == 5 strlen(s) >= 5 //没有'\0' 长度不确定
char s[] = {"abcde"}; //自动生成'\0' <=> char s[] = {'a', 'b', 'c', 'd', 'e', '\0'}; //s字符数组,与普通数组是一样的,保存在一个.data/栈空间 //数组区域是可读可写的 sizeof(s) == 6 strlen(s) == 5
char s[] = {"abcde"}; s[1] = 'B'; //OK char ch = s[1]; //OK *(s + 1) = 'B'; //OK ch = *(s + 1); //OK s = s + 1; //ERROR //s是数组名,数组名当作指针,指针常量 //s的指向 是不能够改变的,但是s指向的内存空间 //里面的内容是可变的 printf("%s\n", s); //abcde %s : char* 地址 //把后面的那个地址当作是一个字符串的首地址,一个一个字符进行输出,直到遇到\0才会结束!
函数指针
函数也有地址 ,那么咱们可以定义一个指针,去存放一个函数的地址,像这样的指针,称为函数指针。
函数指针如何定义
int * abc(int a, float b) { }
描述函数abc的类型: int (int , float ) : 是一个返回值为int类型,带有一个Int类型和float类型的参数的函数类型
指向函数的返回值的类型 (*指针变量名) (指向函数的形参类型列表);
int (* p)(int, int) ;
定义了一个指针变量p,p是一个函数指针,是用来指向一个返回值为int类型,并且带有两个int类型参数的函数的。
函数指针如何赋值
&函数名
or
函数名 : 在C语言中,函数名本身就可以代表函数的地址
通过函数指针去调用指向的函数,有如下两种方案:
p为函数的指针。
(1) (*p)(实参列表)
(2) p(实参列表)
int *p=abc//函数名 p(a,b);//调用函数
二级指针 与 多级指针
int a,b;
可以定义一个指针变量p1,来保存变量a的地址:
int *p1=&a; b=*p1=*&a=a;//通过指针p1访问a变量
可以定义一个指针变量p2,来保存变量p1的地址:
int *p2=&p1 int b=**p2=*p1=a//通过指针p2访问a变量
p2保存的是一个一级指针p1的地址,所以说,
p2指向一个一级指针,p2就是一个二级指针
可以定义一个指针变量p3,来保存变量p2的地址:
int *p3=&p2; int b=***p3=**p2=*p1=a;
p3保存的是一个二级指针p2的地址,所以说,
p3指向一个二级指针,p3就是一个三级指针
加载全部内容