C语言指针

在C语言中,有三个函数可以用来在显示器上输出数据

puts():只能输出字符串,并且输出结束后会自动换行。

putchar():只能输出单个字符。

printf():可以输出各种类型的数据。

但是,printf() 是最灵活、最复杂、最常用的输出函数,完全可以替代 puts() 和 putchar()

printf() 格式控制符的完整形式如下:
%[flag][width][.precision]type
[ ] 表示此处的内容可有可无,是可以省略的。
1) type 表示输出类型,比如 %d、%f、%c、%lf,type 就分别对应 d、f、c、lf;再如,%-9d 中 type 对应 d。
type 这一项必须有,这意味着输出时必须要知道是什么类型。
2) width 表示最小输出宽度,也就是至少占用几个字符的位置;例如,%-9d 中 width 对应 9,表示输出结果最少
占用 9 个字符的宽度。
当输出结果的宽度不足 width 时,以空格补齐(如果没有指定对齐方式,默认会在左边补齐空格);当输出结果的
宽度超过 width 时,width 不再起作用,按照数据本身的宽度来输出。

指针

指针变量也可以连续定义,例如:
int *a, *b, *c; //a、b、c 的类型都是 int*
注意每个变量前面都要带*。如果写成下面的形式,那么只有 a 是指针变量,b、c 都是类型为 int 的普通变量:
int *a, b, c;

指针变量的运算(加法、减法和比较运算)

指针 变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等,
请看下面的代码:
#include <stdio.h>

int main(){
    int    a = 10,   *pa = &a, *paa = &a;
    double b = 99.9, *pb = &b;
    char   c = '@',  *pc = &c;
    //最初的值
    printf("&a=%#X, &b=%#X, &c=%#X\n", &a, &b, &c);
    printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
    //加法运算
    pa++; pb++; pc++;
    printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
    //减法运算
    pa -= 2; pb -= 2; pc -= 2;
    printf("pa=%#X, pb=%#X, pc=%#X\n", pa, pb, pc);
    //比较运算
    if(pa == paa){
        printf("%d\n", *paa);
    }else{
        printf("%d\n", *pa);
    }
    return 0;
}
运行结果:
&a=0X28FF44, &b=0X28FF30, &c=0X28FF2B
pa=0X28FF44, pb=0X28FF30, pc=0X28FF2B
pa=0X28FF48, pb=0X28FF38, pc=0X28FF2C
pa=0X28FF40, pb=0X28FF28, pc=0X28FF2A
2686784
从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是 int、double、char 类型的
长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。
不过 C 语言 并没有规定变量的存储方式,如果连续定义多个变量,它们有可能是挨着的,也有可能是分散的,这取 决于变量的类型、编译器的实现以及具体的编译模式,所以 对于指向普通变量的指针,我们往往不进行加减运算, 虽然编译器并不会报错,但这样做没有意义,因为不知道它后面指向的是什么数据。

针变量除了可以参与加减运算,还可以参与比较运算。当对指针变量进行比较运算时,比较的是指针变量本身的 值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据,否则就指向不同的数据。
上面的代码(第一个例子)在比较 pa 和 paa 的值时,pa 已经指向了 a 的上一份数据,所以它们不相等。而 a 的上一份数据又不知道是什么,所以会导致 printf() 输出一个没有意义的数,这正好印证了上面的观点, 不要对指 向普通变量的指针进行加减运算。
另外需要说明的是,不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义。

C 语言数组指针(指向数组的指针)

数组名可以认为是一个 指针 ,它指向数组的第 0 个元素。 C 语言 中, 我们将第 0 个元素的地址称为数组的首地址。
数组名的本意是表示整个数组,也就是表示多份数据的集合,但在使用过程中经常会转换为指向数组第 0 个元素
的指针,所以上面使用了“认为”一词,表示数组名和数组首地址并不总是等价。
数组和指针等价起来是为了方便大家理解(在大多数情况下数 组名确实可以当做指针使用)
数组和指针不等价的一个典型案例就是求数组的长度,这个时候只能使用数组名,不能使用数组指针
与普通变量名相比,数组名既有一般性也有特殊性:一般性表现在数组名也用来指代特定的内存块,也有类型 和长度;特殊性表现在数组名有时候会转换为一个指针,而不是它所指代的数据本身的值。
数组是一系列数据的集合,没有开始和结束标志, p 仅仅是一个指向 int 类型的指针,编译器不知道它指向的是 一个整数还是一堆整数,对 p 使用 sizeof 求得的是指针变量本身的长度。也就是说, 编译器并没有把 p 和数组 关联起来,p 仅仅是一个指针变量,不管它指向哪里,sizeof 求得的永远是它本身所占用的字节数。
数组也有类型,这是很多读者没有意识到的,大部分 C 语言书籍对这一点也含糊其辞!我们可以将 int 、 float、 char 等理解为基本类型,将数组理解为由基本类型派生得到的稍微复杂一些的类型。 sizeof 就是根据符号的类型来计算长度的。
对于数组 a ,它的类型是 int [6] ,表示这是一个拥有 6 int 数据的集合, 1 int 的长度为 4 6 int 的长度为 4 × 6 = 24 sizeof 很容易求得。
对于指针变量 p ,它的类型是 int * ,在 32 位环境下长度为 4 ,在 64 位环境下长度为 8
归根结底, a p 这两个符号的类型不同,指代的数据也不同,它们不是一码事, sizeof 是根据符号类型来求 长度的,a p 的类型不同,求得的长度自然也不一样。

C 语言字符串指针(指向字符串的指针)

char str[] = "hello";     //字符数组
char *str = "hello";   //指针指向字符串
它们都可以使用%s 输出整个字符串,都可以使用*或[ ]获取单个字符,这
两种表示字符串的方式是不是就没有区别了呢?
有!它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存 储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其 他数据)只有读取权限,没有写入权限
内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符 串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。
第二种形式的字符串称为 字符串常量 ,意思很明显,常量只能读取不能写入
#include <stdio.h>
int main(){
    char *str = "Hello World!";
    str = "I love C!";  //正确
    str[3] = 'P';  //错误

    return 0;
}

这段代码能够正常编译和链接,但在运行时会出现段错误(Segment Fault)或者写入位置错误。
第4行代码是正确的,可以更改指针变量本身的指向;第5行代码是错误的,不能修改字符串中的字符。

到底使用字符数组还是字符串常量?
在编程过程中如果只涉及到对字符串的读取,那么字符数组和字符串常量都能够满足要求;如果有写入(修改)操 作,那么只能使用字符数组,不能使用字符串常量。

指针变量作为函数参数

需要强调的是,不管使用哪种方式传递数组,都不能在函数内部求得数组长度,因为 intArr 仅仅是一个指针,而不 是真正的数组,所以必须要额外增加一个参数来传递数组长度
参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到 另一块内存上。
用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、 局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C 语言没有任何机制来保证这些数据会一直有效, 它们在后续使用过程中可能会引发运行时错误。

二维数组指针(指向二维数组的指针)

int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a;
括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为 int [4],这正是 a 所包含的每个一维数组的类型。
[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作 int *p[4],那么应该理解为 int *(p[4]),p 就成了一个指针数组,而不是二维数组指针
数组名 a 在表达式中也会被转换为和 p 等价的指针!
下面我们就来探索一下如何使用指针 p 来访问二维数组中的每个元素。按照上面的定义:
1) p 指向数组 a 的开头,也即第 0 行;p+1 前进一行,指向第 1 行。
2) *(p+1)表示取地址上的数据,也就是整个第 1 行数据。注意是一行数据,是多个数据,不是第 1 行中的第 0 个
*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针; 就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
*(*(p+1)+1)表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据。
根据上面的结论,可以很容易推出以下的等价关系:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
总结
int *p1[5];//指针数组
int *(p2[5]);//指针数组,和上面的形式等价
int (*p3)[5]; //二维数组指针
int (*func)(int,int); //函数指针
为了能够通过指针来遍历数组元素,在定义数组指针时需要进行降维处理,例如三维数组指针实际指向的数据 类型是二维数组,二维数组指针实际指向的数据类型是一维数组,一维数组指针实际指向的是一个基本类型;
在表达式中,数组名也会进行同样的转换(下降一维)
C 语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。对,
从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!
对于初学者,有几种运算符的优先级非常容易混淆,它们的优先级从高到低依次是:
定义中被括号 ( ) 括起来的那部分。
后缀操作符:括号 ( ) 表示这是一个函数,方括号 [ ] 表示这是一个数组。
前缀操作符:星号 * 表示“指向 xxx 的指针
两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相 差的元素个数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值