您的位置:首页 > 编程语言 > C语言/C++

C语言学习笔记-指针

2017-02-28 17:39 253 查看
对星号
*
的总结

在我们目前所学到的语法中,星号
*
主要有三种用途:

表示乘法,例如
int a = 3, b = 5, c;  c = a * b;
,这是最容易理解的。
表示定义一个指针变量,以和普通变量区分开,例如
int a = 100;  int *p = &a;

表示获取指针指向的数据,是一种间接操作,例如
int a, b, *p = &a;  *p = 100;  b =
*p;


通过指针交换两个变量的值:

#include <stdio.h>
int main(){
int a = 100, b = 999, temp;
int *pa = &a, *pb = &b;
printf("a=%d, b=%d\n", a, b);
/*****开始交换*****/
temp = *pa;  //将a的值先保存起来
*pa = *pb;  //将b的值交给a
*pb = temp;  //再将保存起来的a的值交给b
/*****结束交换*****/
printf("a=%d, b=%d\n", a, b);
return 0;
}
运行结果:
a=100, b=999
a=999, b=100
---------------------------------------------华丽分割线------------------------------------

int    a = 10,   *pa = &a, *paa = &a;
double b = 99.9, *pb = &b;
char   c = '@',  *pc = &c;


//加法运算
pa++; pb++; pc++;
//减法运算
pa -= 2; pb -= 2; pc -= 2;


pa、pb、pc
每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。

---------------------------------------------华丽分割线------------------------------------

#include <stdio.h>
int main(){
int arr[] = { 99, 15, 100, 888, 252 };
int len = sizeof(arr) / sizeof(int);  //求数组长度
int i;
for(i=0; i<len; i++){
printf("%d  ", *(arr+i) );  //*(arr+i)等价于arr[i]
}
printf("\n");
return 0;
}


运行结果:
99  15  100  888  252


8 行代码中我们使用了
*(arr+i)
这个表达式,arr
是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,*(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。

int arr[] = { 99, 15, 100, 888, 252 };
int *p = arr
arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以
int
*p = arr;
也可以写作
int
*p = &arr[0];
。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。“arr
本身就是一个指针”这种表述并不准确,严格来说应该是“arr 被转换成了一个指针”。如果一个指针指向了数组,我们就称它为数组指针(Array
Pointer)。

---------------------------------------------华丽分割线------------------------------------

C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中。

char str[] = "http://c.biancheng.net";字符数组归根结底还是一个数组

除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串:

char *str = "http://c.biancheng.net";
char *str;
str = "http://c.biancheng.net";
字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0  个字符的地址称为字符串的首地址。字符串中每个字符的类型都是
char
,所以
str 的类型也必须是
char
*


这一切看起来和字符数组是多么地相似,它们都可以使用
%s
输出整个字符串,都可以使用
*
[
]
获取单个字符,这两种表示字符串的方式是不是就没有区别了呢?

有!它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。

#include <stdio.h>
int main(){
char *str = "Hello World!";
str = "I love C!";  //正确
str[3] = 'P';  //错误
return 0;
}
这段代码能够正常编译和链接,但在运行时会出现段错误(Segment
Fault)或者写入位置错误。
第4行代码是正确的,可以更改指针变量本身的指向;第3行代码是错误的,不能修改字符串中的字符。在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操作,那么只能使用字符数组,不能使用字符串常量。

最后我们来总结一下,C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取和修改,而字符串常量只能读取不能修改。

---------------------------------------------华丽分割线------------------------------------

数组是一系列数据的集合,无法通过参数将它们一次性传递到函数内部,如果希望在函数内部操作数组,必须传递数组指针

C语言为什么不允许直接传递数组的所有元素,而必须传递数组指针呢?

参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。

对于像 int、float、char 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。而数组是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行内存拷贝有可能是一个漫长的过程,会严重拖慢程序的效率,为了防止技艺不佳的程序员写出低效的代码,C语言没有从语法上支持数据集合的直接赋值。

---------------------------------------------华丽分割线------------------------------------

前面我们说函数运行结束后会销毁所有的局部数据,这个观点并没错,大部分C语言教材也都强调了这一点。但是,这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。对于上面的两个例子,func()
运行结束后 n 的内存依然保持原样,值还是 100,如果使用及时也能够得到正确的数据,如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义。

---------------------------------------------华丽分割线------------------------------------

指针可以指向一份普通类型的数据,例如
int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double *、char * 等。

如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

将这种关系转换为C语言代码:

int a =100;
int *p1 = &a;
int **p2 = &p1;

指针变量也是一种变量,也会占用存储空间,也可以使用
&
获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号
*
。p1
是一级指针,指向普通类型的数据,定义时有一个
*
;p2
是二级指针,指向一级指针 p1,定义时有两个
*


如果我们希望再定义一个三级指针 p3,让它指向 p2,那么可以这样写:

int ***p3 = &p2;

四级指针也是类似的道理:

int ****p4 = &p3;

实际开发中会经常使用一级指针和二级指针,几乎用不到高级指针。

---------------------------------------------华丽分割线------------------------------------


空指针 NULL

一个指针变量可以指向计算机中的任何一块内存,不管该内存有没有被分配,也不管该内存有没有使用权限,只要把地址给它,它就可以指向,C语言没有一种机制来保证指向的内存的正确性,程序员必须自己提高警惕。

我强烈建议对没有初始化的指针赋值为
NULL,例如:

char *str = NULL;
NULL 是“零值、等于零”的意思,在C语言中表示空指针。从表面上理解,空指针是不指向任何数据的指针,是无效指针,程序使用它不会产生效果。

其实,NULL
是在
stdio.h
中定义的一个宏,它的具体内容为:

#define NULL ((void *)0)
(void
*)0
表示把数值 0 强制转换为
void
*
类型,最外层的
(
)
把宏定义的内容括起来,防止发生歧义。从整体上来看,NULL 指向了地址为 0 的内存,而不是前面说的不指向任何数据。

---------------------------------------------华丽分割线------------------------------------


void 指针

void 用在函数定义中可以表示函数没有返回值或者没有形式参数,用在这里表示指针指向的数据的类型是未知的。

也就是说,
void
*
表示一个有效指针,它确实指向实实在在的数据,只是数据的类型尚未确定,在后续使用过程中一般要进行强制类型转换。
C语言动态内存分配函数 malloc() 的返回值就是
void
*
类型,在使用时要进行强制类型转换,请看下面的例子:

#include <stdio.h>
int main(){
//分配可以保存30个字符的内存,并把返回的指针转换为 char *
char *str = (char *)malloc(sizeof(char) * 30);
gets(str);
printf("%s\n", str);
return 0;
}
这里重点是让大家理解
void
*
,它不是空指针的意思,而是实实在在的指针,只是指针指向的内存中不知道保存的是什么类型的数据。

---------------------------------------------华丽分割线------------------------------------

对于二维数组,也是类似的道理,例如
int
a[3][3]={1, 2, 3, 4, 5, 6, 7, 8, 9};
,它的类型是
int
[3][3]
,长度是 4×3×3 = 36,读者可以亲自测试。

---------------------------------------------华丽分割线------------------------------------

C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof
或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。

假设现在有一个数组
a 和指针变量 p,它们的定义形式为:

int a = {1, 2, 3, 4, 5}, *p, i = 2;

读者可以通过以下任何一种方式来访问 a[i]:
p = a;

p[i];
p = a;

*(p + i);
p = a + i;

*p;
对数组的引用 a[i] 在编译时总是被编译器改写成
*(a+i)
的形式,C语言标准也要求编译器必须具备这种行为。

---------------------------------------------华丽分割线------------------------------------


C语言指针数组(每个元素都是指针)

如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:

dataType *arrayName[length];
[ ]
的优先级高于
*
,该定义形式应该理解为:

dataType *(arrayName[length]);
括号里面说明
arrayName
是一个数组,包含了
length
个元素,括号外面说明每个元素的类型为
dataType
*


除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,下面是一个简单的例子:

---------------------------------------------华丽分割线------------------------------------

为了更好的理解指针和二维数组的关系,我们先来定义一个指向
a 的指针变量 p:

int (*p)[4] = a;
括号中的
*
表明
p 是一个指针,它指向一个数组,数组的类型为
int
[4]
,这正是 a 所包含的每个一维数组的类型。

[ ]
的优先级高于
*
(
)
是必须要加的,如果赤裸裸地写作
int
*p[4]
,那么应该理解为
int
*(p[4])
,p 就成了一个指针数组,而不是二维数组指
针,这在《C语言指针数组》中已经讲到。

---------------------------------------------华丽分割线------------------------------------


指针数组和二维数组指针的区别

指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:

int
*(p1[5]);
//指针数组,可以去掉括号直接写作 int *p1[5];

int
(*p2)[5];
//二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的
p2 为例,它占用 4 个字节的内存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言