指针为指针赋值,建立共同指向关系。

指针 引用 解引用 取地址

指针(变量):是一个变量,它存储了一个内存地址

1
2
3
4
5
6
int x = 10;  
int *ptr = &x;
// 获取x的地址

*ptr = 20;
// 修改x的值

引用:是一个别名,它允许使用一个变量的多个名称来访问同一个内存位置,引用可以看作是指针的一种简化形式,它不需要使用*运算符来解引用,也不需要使用&运算符来获取地址,C++专用

1
2
3
4
5
6
int x = 10;  
int &ref = x;
// ref是x的引用

ref = 20;
// 修改x的值

解引用:是指使用*运算符来访问指针所指向的内存位置处的值

1
2
3
4
int x = 10;
int *ptr = &x;
int y = *ptr;
// 解引用ptr,获取x的值

取地址:是指使用&运算符来获取变量的地址

1
2
3
int x = 10;  
int *ptr = &x;
// 获取x的地址

指针变量的大小取决于地址的大小

32位平台下是32个bit位(4字节)

64位平台下是64个bit位(8字节)

指针可以被重新赋值指向另一个内存位置,而引用一旦绑定到一个变量上就不能再绑定到另一个变量上。

那么思考,是否可以对引用取地址呢?如果可以,是否可以对引用解引用呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int a=10;

//用两个引用指向同一个变量

int &ref=a;
int &ref1=a;

//效果和指针指向同一个变量一样

int *p=&a;

//是否可以对引用取地址?

cout<<&ref<<" "<<&ref1<<" "<<p<<endl;

//是否可以对引用解引用?如果是引用的指针呢?

int *pref1=&ref;
cout<<pref1<<" "<<*pref1<<endl;

结构体对象.成员名或者结构体对象指针->成员名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>

struct stu{
char name[20];
int age;
char sex[10];
char tel[20];
};

void print(struct stu *s){
//传入指针变量,存放结构体对象的地址,对传入的地址解引用,使用`结构体对象.成员名`
printf("%s %d %s %s\n",(*s).name,(*s).age,(*s).sex,(*s).tel);
//无需解引用,使用`结构体对象指针->成员名`
printf("%s %d %s %s\n",s->name,s->age,s->sex,s->tel);
}

int main(){
//初始化结构体对象
struct stu s={"张三",20,"male","0411733642"};
printf("%s %d %s %s\n",s.name,s.age,s.sex,s.tel);
//传入结构体的地址
print(&s);
return 0;
}

基本概念

指针是内存中一个最小单元的编号,也就是地址,平时我们说的指针,通常是指指针变量,那是用来存放内存地址的变量。换言之,指针就是地址。

1
2
3
int a=10;
int* pa=&a;
int** ppa=&pa;

如何理解?对于int* pa=&a;*表示pa是一个指针变量,int则表示pa指向的变量是一个整型变量;对于int** ppa=&pa;*表示ppa是一个指针变量,int*则表示ppa指向的变量是一个整型指针变量。

在64位操作系统中,指针都占8字节;指针是首地址,且指针对应内存,不同指针类型对应的内存大小不同。

int型指针,对应内存为4个字节;int *p; p++;意思就是下移一个指针地址,该地址对应的内存占用4个字节(4个内存单元)。

char型指针,对应内存为1个字节;char *q; q++;意思则是下移一个指针地址,该地址对应的内存占用1个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>

int main(){
int a=10; //向内存申请4字节,存储10
int *p=&a; //p为整型指针变量(存放指针(地址)的变量)
*p=100;
printf("%p\n",p);
printf("a=%d\n",a);
p++;
printf("%p\n\n",p);

char b='s'; //向内存申请1字节,存储s
char *q=&b; //q为字符型指针变量
*q='o';
printf("%p\n",q);
printf("b=%c\n",b);
q++;
printf("%p\n",q);

return 0;
}

传参

经典案例swap,交换两个变量的值。如果传值,函数中的形参和局部变量temp在函数调用完成之后即释放,实参不会发生改变;如果要交换实参的值,必须要传地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>

void swap(int *a,int *b){
int temp=*a;
*a=*b;
*b=temp;
}

int main(){
int a=4,b=5;
swap(&a,&b);
printf("%d %d",a,b);
return 0;
}

指针可以赋值运算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>

int main(){
// int a,*pa=&a,*pb;pb=pa;
int x=3,y=0,*px=&x;
y=*px+5; // 将y的值赋为指针px所指向的变量x的值加上5,即y = 8。
y=++*px; // 首先对指针px所指向的变量x进行前置自增操作,将x的值增加1,然后将y赋为新的x的值,即y = 4。
printf("x=%d,y=%d\n",x,y);
x=3,y=0;
y=*px++; //指针px所指向的变量x的值赋给y,即y = 3,然后指针px进行后置自增操作,将px指向下一个整型变量的地址。
printf("x=%d,y=%d\n",x,y);
x=3,y=0;
y=(*px)++; //尝试先取出px指针指向的变量的值,然后将该值自增1。但是,这个表达式还会尝试将自增前的值赋给y,这样就对同一个变量进行了两次修改。
printf("x=%d,y=%d\n",x,y);
return 0;
}

对于y=(*px)++;,y的结果是不确定的,具体取决于编译器的实现。因此应该避免同时对同一个对象进行多次修改。

字符串拷贝

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>

void mystrcpy(char *dest,const char *src);

int main(){
char str1[32]="hello";
char str2[32]="123456789";

mystrcpy(str2,str1);
printf("%s",str2);
return 0;
}

如何写mystrcpy这个函数?传入两个字符型指针变量,需要依次让src指向的值赋值给dest指向的值,然后再分别自增,所以使用后置自增;判断一个字符串是否结束的标志就是看是否遇到'\0',所以循环终止的条件为src!='\0'

1
2
3
4
5
6
7
void mystrcpy(char *dest,char *src){
while(*src!='\0'){
*dest++=*src++;
}
}

// hello6789

看似可以,然而结果却没有把目标串中的后面4个字符屏蔽掉,也就是src串末尾的'\0'并没有传入dest串。所以应当先赋值再判断。

1
2
3
4
5
6
7
8
void mystrcpy(char *dest,char *src){
while((*dest=*src)!='\0'){
dest++;
src++;
}
}

// hello

注意main函数中的一个细节,为什么用const关键字,准确来说修饰的是src指向的值是不可更改的。

要分清用const修饰指针变量还是变量,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void f(){
int num;

const int *p1=&num;
//(*p1)++; 错误
p1++;

int *const p2=&num;
(*p2)++;
//p2++; 错误

const int *const p3=&num;
//(*p3)++; 错误
//p3++; 错误
}

使用内存

比如int *p=NULL; *p=100,访问了不能访问的内存,如何避免段错误?如何合法地使用内存?

  • 系统分配内存
1
2
int a;
int *p=&a;
  • 用户申请内存(堆内存)
1
2
3
char *str=(char *)malloc(32);
free(str);
str=NULL;

使用动态内存分配函数malloc来分配了32字节的内存,并将其地址赋给了指针str。在堆上分配32字节的内存空间,并将其地址保存在指针str中。

在使用完分配的内存后,需要通过调用free(str)来释放内存,以避免内存泄漏,使用时需要引入stdlib头文件;另外,由于内存已经释放了,但是str仍然指向那片区域,为了避免野指针,还需要将str置为NULL。

指针与数组

数组名和指针都可以用于访问内存地址,但它们有许多重要区别。

数组名 指针
类型 数组名表示整个数组本身,是一个不可修改的常量指针,它包含的是数组第一个元素的地址,因此,可以使用数组名来引用整个数组而不是单个元素。 指针是一个变量,存储了另一个变量的地址。
大小 数组的大小在编译期间就确定了,所以一个数组一旦声明就不能更改大小。 指针的大小取决于系统使用的位数,通常是32位或64位,所以它可以指向任何大小的数据类型。
操作 数组名代表常量指针,不能作为左值被赋值,也不能修改。但是,通过下标进行索引,在数组位置替换新值是允许的。 指针是一个可寻址的变量,因此赋值操作是合法的,可以将指针指向不同位置的数据。
1
2
3
4
5
6
char str[32]="helloworld";
char *p="helloworld";
str++; // 数组名是常指针(地址常量),不能修改,不合法操作
p++; // 指向下一个元素
str[5]=' '; // 字符数组,合法操作
p[5]=' '; // 字符串常量,不能修改,不合法操作

上述代码中声明了一个指针变量p,并初始化为指向字符串常量helloworld的地址。注意,helloworld是一个字符串常量,它存放在只读数据区,因此不能被修改。在代码执行过程中,指针p是可以被修改的,即p++是有效的操作,它将p指向下一个字符。这是因为指针变量p本身是一个普通的变量,存放在栈空间中,它只是保存了字符串常量helloworld的地址。数组str被声明为字符数组,并初始化为helloworld。数组名是一个常量指针(也可以看作是一个常量),它指向存放字符串的内存地址。因此,str++是不允许的操作,由于数组名是常量指针,它的值不能被修改。声明字符数组并对其进行初始化时,可以修改数组中的各个元素,包括通过下标进行修改。

So, 什么是常量指针?

  • 不动指针:const修饰指针,指针不可再移动
1
2
int *const q;
q=p+1;//不合法操作
  • 不写指针:const修饰数据,指针所指向的目标不可被修改
1
2
3
4
int *p=malloc(8);
const *int q;
q=p;//指针自身可以被赋值
*q=5;//不合法操作

思考:求以下p1[0]p2[0]p3[0]的值。

1
2
3
4
int a[5]={1,2,3,4,5};
int *p1=(int*)(&a+1);
int *p2=(int*)(int(a)+1);
int *p3=(int*)(a+1);

&a表示数组的地址,a表示数组首元素地址。&a+1,则指的是此数组之后的20个字节地址,int*强转,则p1指向这20个字节地址中的首元素地址;int(a)+1表示先强转为整数然后直接加1,所以p2指向第一组4字节中的第2个字节,然而在C语言中,如果要访问一个整数,一定是从整数的第一个字节开始访问;如果首地址为0X100,那么a+1则表示0X104,所以p3指向的元素为2。

指针与字符串

指针数组中的元素是字符串指针,需要使用索引来逐个访问并输出各个字符串。

1
2
3
4
5
6
7
#include<stdio.h>

int main(){
char *string[]={"I love China!","I am"};
printf("%s\n",string);
return 0;
}

观察上述代码,string是一个指针数组,顾名思义,该数组中的每一个元素存放的是一个指针。在这个例子中,string含有16个字节,其次,I love China!I am作为字符串常量,存于只读数据区中,最终需要打印一个字符串,然而string是一个字符串指针数组,所以需要使用 string[0]string[1] 分别打印数组中的两个字符串。

1
2
3
4
5
6
7
8
9
10
#include<stdio.h>

int main(){
char *string[]={"I love China!","I am"};
printf("%s %s\n",string[0],string[1]);
printf("%s %s\n",*string,*(string+1));
return 0;
}
// I love China! I am
// I love China! I am

数组指针

指针数组是一个数组,数组的每一个元素都是指针;数组指针是一个指针!该指针指向数组(一维二维均可以)

1 2 3 4
5 6 7 8
9 10 11 12

如图int a[3][4],C语言中,对于数组,数组名是数组的第一个元素的地址,对于该二维数组,a[0]表示首行首元素地址,a表示数组首行地址,&a表示二维数组地址,虽然三个地址是一样的,但是+1就不一样了,a[0]+1加4个字节,a+1加16个字节,&a+1加48个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>

int main(){
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("a[0]=%p a=%p &a=%p\n",a[0],a,&a);
printf("a[0]=%p a=%p &a=%p\n",a[0]+1,a+1,&a+1);
int i,j;
for(i=0;i<3;i++){
for(j=0;j<4;j++){
printf("%d ",a[i][j]);
}
printf("\n");
}
}
// a[0]=000000b7e85ff6a0 a=000000b7e85ff6a0 &a=000000b7e85ff6a0
// a[0]=000000b7e85ff6a4 a=000000b7e85ff6b0 &a=000000b7e85ff6d0
// 1 2 3 4
// 5 6 7 8
// 9 10 11 12

用数组指针来表示二维数组,int(*p)[4],指针p指向一维数组,一维数组含4(二位数组的列数)个元素,指针p按行顺序向下移动,所以等价于二级指针a,所以a[i][j]等价于p[i][j],用指针来表示也就是*(*(p+i)+j)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>

int main(){
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int i,j;

int(*p)[4]=a;
for(i=0;i<3;i++){
for(j=0;j<4;j++){
// printf("%d ",p[i][j]);
printf("%d ",*(*(p+i)+j));
}
printf("\n");
}
}

用指针数组来表示二维数组,需要初始化int *q[],数组q中每个元素是int *类型的指针,对于二维数组而言,int *是每个元素的指针,而a[0]表示首行首元素地址,那么紧接着分别是a[1]a[2],因此初始化为int *q[3]={a[0],a[1],a[2]};,其次如何代替循环中的循环次数,分别可以改为i<sizeof(a)/sizeof(a[0])j<sizeof(a[0])/sizeof(a[0][0])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>

int main(){
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int i,j;

int *q[3]={a[0],a[1],a[2]};
for(i=0;i<3;i++){
for(j=0;j<4;j++){
printf("%d ",q[i][j]);
}
printf("\n");
}
}

用指针来表示二维数组,当成一维数组来输出。(一级地址)

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>

int main(){
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int i,j;

int *r=a[0];
for(i=0;i<12;i++){
printf("%d ",r[i]);
}
printf("\n");
}

&a[1][2]<==>a[1]+2<==>*(a+1)+2 第1行第2列元素地址, a[1][2]<==>*(a[1]+2)<==>*(*(a+1)+2) 第1行第2列元素的值。

函数指针与指针函数

函数指针

在C中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。把函数的首地址(函数入口地址)赋予一个指针变量,使该指针变量指向该函数,然后通过指针变量就可以找到并调用该函数(函数指针变量)。

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
int add(int x,int y){
return x+y;
}

int main(){
int(*p)(int,int);
p=add;
printf("%d",p(1,2));
return 0;
}

也可以声明一个新的类型表示函数指针类型。

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
typedef int(*T)(int,int);

int add(int x,int y){
return x+y;
}

int main(){
T q=add; // 等价于int(*q)(int,int);
printf("%d",q(1,2));
return 0;
}

指针函数

返回值为指针的函数,在C中,允许一个函数的返回值为一个指针(地址),这种返回指针值的函数称为指针型函数,简称指针函数。不可以返回局部变量的地址!!!栈内存释放,堆内存保留

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

char *init(){
char *str=(char *)malloc(sizeof(char)*128);
return str;
}

int main(){
char *str=init();
strcpy(str,"hello");
printf("%s",str);
free(str);
return 0;
}

指针的指针

上面我们用指针函数返回指针变量,试想如果无返回值,应该怎么实现字符串的复制?

传入参数一定是地址,否则局部变量会在函数调用完成之后释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void init(char **s){
*s=(char *)malloc(sizeof(char)*128);
}

int main(){
char *str=NULL;
init(&str);
strcpy(str,"hello");
printf("%s",str);
free(str);
return 0;
}

当我们想要在函数中修改一个指针变量的值,并使这个修改对外部生效时,我们需要使用指向指针的指针作为函数参数,通过间接访问来修改原始指针的值。

回调函数

把函数名作为另一个函数的参数,用来修改函数的功能。比如下列的冒泡排序,一般的做法是升序要么降序来决定是a[j+1]>a[j]还是a[j+1]<a[j]

1
2
3
4
5
6
7
8
9
10
11
12
void sort(int *a,int length){
int i,j,t;
for(i=0;i<length;i++){
for(int j=0;j<length-1-i;j++){
if(a[j]>a[j+1]){
t=a[i];
a[i]=a[j+1];
a[j+1]=t;
}
}
}
}

使用回调函数,定义两个辅助函数:less和greater,根据传入的函数指针实现不同的比较方式,从而实现不同的排序需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<stdio.h>

int less(int x,int y){
return x<y?1:0;
}

int greater(int x,int y){
return x>y?1:0;
}

void sort(int *a,int length,int(*p)(int,int)){
int i,j,t;
for(i=0;i<length;i++){
for(int j=0;j<length-1-i;j++){
if(p(a[j],a[j+1])){
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
}

int main(){
int a[10]={0};
int i;
for(i=0;i<10;i++){
scanf("%d",&a[i]);
}
sort(a,10,greater); // 通过传递greater函数指针实现从大到小排序
for(i=0;i<10;i++){
printf("%d ",a[i]);
}
return 0;
}

复杂类型声明

对于下面这种丧心病狂的表示,我也是有点无语,但是却不难理解,只要遵循右左法则。

1
int *(*(*fp)(int))[10];

首先是指针fp指向某个函数,其参数为整型,返回值是一个指针,指向一个指针数组,数组有10个元素,每个元素都是int *类型的。

1
int *(*(*array[5])())();

首先是指针数组,内有五个元素,指向某个函数,函数里没有参数,返回值是一个指针,指向另一个函数,该函数没有参数,返回值也是一个int *类型的整型指针。

指针补充

指向同一块空间的指针除了可以相减,还可以比较大小,如下例所示:

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>

#define N 5
int main(){
float values[N];
float *vp;
for(vp=&values[N];vp>&values[0];){
*--vp=0;
}
return 0;
}

思考,这样写是否可以?

1
2
3
for(vp=&values[N-1];vp>=&values[0];vp--){
*vp=0;
}

vp=values后,vp--,vp指向数组前面的内存地址,vp<values,虽然绝大部分编译器上是可以顺利完成的,然而还是应该避免这样写,标准规定,允许指向元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但不允许与指向第一个元素之前的那个内存位置的指针比较。

结构体传参

结构体面临传参的时候,最好传结构体的地址。因为函数传参的时候,参数是需要压栈的,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能下降。

指针与结构体练习题

设单链表的头指针为L 结点结构由data和next指针两个域构成,其中data域为字符型,设计算法判断该链表的全部n个字符是否中心对称,例如xyx,xyyx都是中心对称

因为要判断左右半部份是否一样,所以使用栈来存储前半部分,LIFO,使用头插法将前半部分存储在单链表中;使用快慢指针,如果链表长为奇数,快指针到头,慢指针恰好指在中点位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

struct ListNode{
char data;
struct ListNode* next;
};

bool isPalindrome(struct ListNode* head){
struct ListNode* slow = head;
struct ListNode* fast = head;
struct ListNode* stack = NULL;

while(fast&&fast->next){
struct ListNode* newNode = malloc(sizeof(struct ListNode));
newNode->data=slow->data;
newNode->next=stack;
stack=newNode;

slow=slow->next;
fast=fast->next->next;
}
if(fast){
slow=slow->next;
}
while(slow){
if(slow->data!=stack->data){
return false;
}
slow=slow->next;
stack=stack->next;
}
return true;
}

int main(){
struct ListNode* node1 = malloc(sizeof(struct ListNode));
struct ListNode* node2 = malloc(sizeof(struct ListNode));
struct ListNode* node3 = malloc(sizeof(struct ListNode));
struct ListNode* node4 = malloc(sizeof(struct ListNode));

node1->data = 'x';
node2->data = 'y';
node3->data = 'y';
node4->data = 'x';

node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;

bool result = isPalindrome(node1);
printf("Is the linked list palindrome? %s\n", result ? "True" : "False");

return 0;
}

输入任意字符串,把所有小写字母转化成大写字母,如有空格,删除空格。例如:输入ABcDEfgH 输出ABCDEFGH

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void deleteSpace(char *str){
while(*str!='\0'){
*str=*(str+1);
str++;
}
}

void aAtransfer(char *dest,char *src){
while(*src!='\0'){
if(*src>='a'&&*src<='z'){
*dest=*src-32;
}else{
*dest=*src;
}
dest++;
src++;
}
}

int main(){
char *str1=(char *)malloc(128);

char ch;
int i=0;
while((ch=getchar())!='\n'){
*(str1+i)=ch; // 将 ch 的值赋给指针 str1 偏移量为 i 的位置
i++;
}
str1[i] = '\0'; // 在字符串末尾加上结束符
// scanf("%[^\n]",str1);
char *head=str1;
char str2[32];
memset(str2,0,sizeof(str2));
while(*str1!='\0'){
if(*str1==' '){
deleteSpace(str1);
}else{
str1++;
}
}
str1=head; // 复位指针 str1 到起始位置
aAtransfer(str2,str1);
// printf("%s\n",str1);
printf("%s",str2);
free(str1);
str1=NULL;
return 0;
}

写一个函数将I am from shanghai 倒置为shanghai from am i。

方法一: 将I am from shanghai当成一个指针数组来存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<stdio.h>
#include<stdlib.h>
#define size 5

int main(){
char *str[5]={0};
int i=0;
for(i=0;i<size;i++){
str[i]=(char *)malloc(sizeof(char)*128);
scanf("%s",str[i]);
}
char *t;
for(int i=0;i<size/2;i++){
t=str[i];
str[i]=str[size-1-i];
str[size-1-i]=t;
}
for(i=0;i<size;i++){
printf("%s ",str[i]);
free(str[i]);
}
return 0;
}

方法二: 将I am from shanghai视为一个完整的字符串作为输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include<stdio.h>
#include<stdlib.h>

void reverseString(char *beg,char *end){
int i=0,length=end-beg+1;
char t;
for(i=0;i<length/2;i++){
t=*beg;
*beg++=*end;
*end--=t;
}
}

int main(){
char *str1=(char*)malloc(sizeof(char)*128);
char ch;
int i=0;
while((ch=getchar())!='\n'){
str1[i++]=ch;
}
str1[i]='\0';

reverseString(str1,str1+i-1);

char *begin=str1,*end=str1;

while(*end!='\0'){
if(*end==' '){
reverseString(begin,end-1);
begin=end+1;
}
end++;
}
reverseString(begin,end-1);
printf("%s",str1);
free(str1);
return 0;
}

在C语言中,指向同一块空间的两个指针之间的减法操作可以得到它们之间的偏移量(以元素数量为单位)。因此,end - beg得到的是end指针距离beg指针的偏移量。

由于数组的索引是从0开始计数的,所以要计算字符串的长度,我们需要将偏移量加1,以包括字符串的末尾标志\0。所以,length = end - beg + 1可以得到beg指向的字符串的实际长度。

需要注意的是,在进行指针运算时,必须确保beg和end指针指向同一个字符串,并且beg指针在end指针之前。否则,指针运算将导致未定义的行为。

思考下列代码输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>

int main(){
int a[3][4]={{1,2,3,4},{3,4,5,6},{5,6,7,8}};
int i;
int (*p)[4]=a,*q=a[0];
for(i=0;i<3;i++){
if(i==0)
(*p)[i+i/2]=*q+1;
else
p++,++q;
}
printf("%d,%d\n",*((int *)p),*q);
return 0;
}

i==0(*p)[0],指针p原来是二级指针,解引用变成一级指针指向首行首列元素,*q+1=1+1=2赋值给首行首列元素。
接着两次p++,++q;
p指向第3行(5,6,7,8),由于(int *)p,经过强转,p指向第3行第1列元素5,外层指针解引用,输出结果5。
q指向第1行第3列元素3,解引用输出结果3。

思考下列代码ptr[0]、ptr[1]、ptr[2]、ptr[3]的值

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>

int main() {
const char *ptr[] = {"Welcome", "to", "beautiful", "Nanjing"};
const char **p=ptr+1;
ptr[0]=(*p++)+2; //
ptr[1]=*(p+1); // Nanjing
ptr[2]=p[1]+3; // jing
ptr[3]=p[0]+(ptr[2]-ptr[1]); // g
return 0;
}

const char **p是一个指向指针的指针,而指针数组ptr每个元素都是一个指针,指向一个字符串常量,对于pre指针而言,初始情况下pre是指向字符串Welcome指针的指针。const char **p=ptr+1,p指向字符串to的指针,字符串to的地址为0X2000,那么经过(*p++)+2ptr[0]不再指向Welcome的指针,指向0X2000+2=0X2002ptr[0]空值;后置的p++,p指向字符串beautiful的指针,接着*(p+1),所以ptr[1]指向Nanjingp[1]+3相当于在*(p+1)的基础上加3,也就是0X4003,所以ptr[2]指向jingptr[2]-ptr[1]等于3,p[0]+3等价于*p+3,p指向ptr[2]ptr[2]指向0X4003,所以p[0]+3等于0X4006,因此ptr[3]指向g