C语言学习笔记-指针
2017-02-28 17:39
253 查看
对星号
在我们目前所学到的语法中,星号
表示乘法,例如
表示定义一个指针变量,以和普通变量区分开,例如
表示获取指针指向的数据,是一种间接操作,例如
通过指针交换两个变量的值:
a=100, b=999
a=999, b=100
---------------------------------------------华丽分割线------------------------------------
pa、pb、pc
每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
---------------------------------------------华丽分割线------------------------------------
运行结果:
99 15 100 888 252
第
8 行代码中我们使用了
是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,*(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。
本身就是一个指针”这种表述并不准确,严格来说应该是“arr 被转换成了一个指针”。如果一个指针指向了数组,我们就称它为数组指针(Array
Pointer)。
---------------------------------------------华丽分割线------------------------------------
C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中。
除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串:
str 的类型也必须是
这一切看起来和字符数组是多么地相似,它们都可以使用
有!它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。
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;
指针变量也是一种变量,也会占用存储空间,也可以使用
是一级指针,指向普通类型的数据,定义时有一个
是二级指针,指向一级指针 p1,定义时有两个
如果我们希望再定义一个三级指针 p3,让它指向 p2,那么可以这样写:
int ***p3 = &p2;
四级指针也是类似的道理:
int ****p4 = &p3;
实际开发中会经常使用一级指针和二级指针,几乎用不到高级指针。
---------------------------------------------华丽分割线------------------------------------
一个指针变量可以指向计算机中的任何一块内存,不管该内存有没有被分配,也不管该内存有没有使用权限,只要把地址给它,它就可以指向,C语言没有一种机制来保证指向的内存的正确性,程序员必须自己提高警惕。
我强烈建议对没有初始化的指针赋值为
NULL,例如:
char *str = NULL;
NULL 是“零值、等于零”的意思,在C语言中表示空指针。从表面上理解,空指针是不指向任何数据的指针,是无效指针,程序使用它不会产生效果。
其实,NULL
是在
#define NULL ((void *)0)
---------------------------------------------华丽分割线------------------------------------
void 用在函数定义中可以表示函数没有返回值或者没有形式参数,用在这里表示指针指向的数据的类型是未知的。
也就是说,
C语言动态内存分配函数 malloc() 的返回值就是
---------------------------------------------华丽分割线------------------------------------
对于二维数组,也是类似的道理,例如
---------------------------------------------华丽分割线------------------------------------
C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof
或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。
假设现在有一个数组
a 和指针变量 p,它们的定义形式为:
int a = {1, 2, 3, 4, 5}, *p, i = 2;
读者可以通过以下任何一种方式来访问 a[i]:
对数组的引用 a[i] 在编译时总是被编译器改写成
---------------------------------------------华丽分割线------------------------------------
如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
dataType *arrayName[length];
dataType *(arrayName[length]);
括号里面说明
除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,下面是一个简单的例子:
---------------------------------------------华丽分割线------------------------------------
为了更好的理解指针和二维数组的关系,我们先来定义一个指向
a 的指针变量 p:
int (*p)[4] = a;
括号中的
p 是一个指针,它指向一个数组,数组的类型为
针,这在《C语言指针数组》中已经讲到。
---------------------------------------------华丽分割线------------------------------------
指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:
int
*(p1[5]);
//指针数组,可以去掉括号直接写作 int *p1[5];
int
(*p2)[5];
//二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的
p2 为例,它占用 4 个字节的内存。
*的总结
在我们目前所学到的语法中,星号
*主要有三种用途:
表示乘法,例如
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 = arrarr 本身就是一个指针,可以直接赋值给指针变量 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)的形式,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语言学习笔记(13指针)
- C语言学习笔记(15指针)
- C语言指针学习笔记:指向结构体的指针
- iOS开发学习笔记 2-9 C语言部分 内存分配函数 函数指针 指针函数 void*
- iOS开发学习笔记 2-4 C语言部分 指针
- what's in string? c语言string类函数实现汇总 都是学习使用指针的好例子啊(算是读书摘抄和笔记吧)
- C语言-指针的基础学习笔记(2)
- c语言学习笔记--指针
- 【C语言学习笔记】指针的“加减”运算
- C语言学习笔记(五)指针
- C语言学习笔记(12指针)
- what's in string? c语言string类函数实现汇总 觉得都是学习使用指针的好例子(算是读书摘抄和笔记吧)
- C语言学习笔记(18多级指针)
- 数组和指针————C语言学习笔记1
- C语言学习笔记.指针1
- C语言学习笔记.指针2
- C语言-指针的基础学习笔记(1)
- C语言学习笔记【指针04】指针数组与指向指针的指针 推荐
- C语言学习笔记之成员数组和指针
- C语言学习笔记7--指针