《C语言程序设计》读书笔记(第10章——指针)

10.1 地址指针的基本概念

10.1.1 数据类型的字节数

首先在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等。如整型量占2个单元,字符量占用1个单元等。

可以用下面的代码来查看各种数据类型占用的字节,即占用的内存单元(不同的电脑和编译环境可能有所不同):

#include 

void main() {
	printf("Size of int is:%d\n", sizeof(int));
	printf("Size of unsigned int is:%d\n", sizeof(unsigned int));
	printf("Size of short is:%d\n", sizeof(short));
	printf("Size of unsigned short is:%d\n", sizeof(unsigned short));
	printf("Size of long is:%d\n", sizeof(long));
	printf("Size of unsigned long is:%d\n", sizeof(unsigned long));
	printf("Size of long long is:%d\n", sizeof(long long));
	printf("Size of unsigned long long is:%d\n", sizeof(unsigned long long));
	printf("Size of char is:%d\n", sizeof(char));
	printf("Size of signed char is:%d\n", sizeof(signed char));
	printf("Size of unsigned char is:%d\n", sizeof(unsigned char));
	printf("Size of float is:%d\n", sizeof(float));
	printf("Size of double is:%d\n", sizeof(double));
	printf("Size of long double is:%d\n", sizeof(long double));
}

《C语言程序设计》读书笔记(第10章——指针)_第1张图片
在上面知道了内存单元的概念后,为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。

根据下面的代码,但现在先不必关心代码的具体内容,可以对地址有个更直观的感受:

#include 

void main(){
	int a=100;		// 4字节 
	int b=5; 	    // 4字节 
	int c=1;   	    // 4字节 
	double d=14.0;	// 8字节 
	double e=1.23;  // 8字节
	double f=12.5;  // 8字节 
	 
	printf("| 变量名 |     值      |       地址       |\n");
	printf("|--------|-------------|------------------|\n");
	printf("|    a   |     %d     |    %d       |\n", a, &a);
	printf("|--------|-------------|------------------|\n");
	printf("|    b   |     %d       |    %d       |\n", b, &b);
	printf("|--------|-------------|------------------|\n");
	printf("|    c   |     %d       |    %d       |\n", c, &c);
	printf("|--------|-------------|------------------|\n");
	printf("|    d   |     %1.1f    |    %d       |\n", d, &d);
	printf("|--------|-------------|------------------|\n");
	printf("|    e   |     %1.2f    |    %d       |\n", e, &e);
	printf("|--------|-------------|------------------|\n");
	printf("|    f   |     %1.1f    |    %d       |\n", f, &f);
	printf("|--------|-------------|------------------|\n");
}

《C语言程序设计》读书笔记(第10章——指针)_第2张图片
代码解释:

  • 其中a变量是一个int变量,从上面代码中知道占4个字节,这里看不出来,但是其他的数据类型看得出来。
  • 其中b变量是一个int变量,地址是6487580,距离a变量的地址为4字节。
  • c变量也是int变量,地址是6487572,距离b变量的地址也是4字节。
  • d变量是double型变量,距离c变量的地址是12字节,并不是double数据类型所占据的8字节。(至于为什么是12而不是8,暂时不清楚,不过可以不用关注这个点,C语言也没有规定说明变量必须是连续存储的。)
  • e变量也是double型变量,距离d变量的地址是8字节,相同数据类型的变量看起来是连续存储的。
  • f变量同样如此。

需要注意的是,内存单元的指针和内存单元的内容是不同的概念。

对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

10.1.2 关于直接访问和间接访问

10.1.2.1 直接访问

a=5;。系统在编译时,已经对变量分配了地址,例如,若变量a分配的地址是2000,则该语句的作用就是把常数5保存到地址为2000的单元。

#include 

void main(){
	// 这是一个名为a的整型变量
	// 该变量的值是5
	// 而为变量a分配的地址是6487580
	// 该语句的作用就是把常数5保存到地址为6487580的单元 
	int a=5;
	// 这是一个名为b的整型变量
	// 该变量的值是6
	// 而为变量b分配的地址是6487576
	// 该语句的作用就是把常数6保存到地址为6487576的单元 
	int b=6;
	// 这是一个名为c的整型变量
	// 该变量的值是7
	// 而为变量c分配的地址是6487572
	// 该语句的作用就是把常数7保存到地址为6487572的单元
	int c=7;
	// 通过&a、&b、&c来输出变量a、b、c的地址 
	printf("&a=%d, &b=%d, &c=%d",&a,&b,&c);
} 

结果:

《C语言程序设计》读书笔记(第10章——指针)_第3张图片

10.1.2.2 间接访问

scanf("%d", &a);。调用函数时,把变量a的地址传弟给函数scanf,函数首先把该地址保存到一个单元中,然后把从键盘接收的数据通过所存储的地址保存到a变量中。

#include 

void main(){
	int a;
	scanf("%d", &a);
	printf("您输入一个整数:%d", a);
} 

10.1.3 指针

在C语言中,指针是一个特殊的变量,存储地址。

假设我们定义了一个指针变量int* i_pointer用来存放整型变量i的地址。可以通过语句i_pointer=&i;。这里的*符号是用来标志该变量是一个指针变量的。

将i的地址(2000)存放到i_pinter中。这时,i_pointer的值就是(2000),即变量i所占用单元的起始地址。

要存取变量i的值,可以采用间接方式:先找到存放"i的地址"的变量i_pointer,从中取出i的地址(2000),然后就可以取出i的值了。

图形表示:

《C语言程序设计》读书笔记(第10章——指针)_第4张图片

#include 

void main(){
	// 这是一个名为i,值为3的普通变量,可以通过&i打印出该变量的地址 
	int i=3;
	printf("变量i的地址为:%d\n", &i);
	
	// 这里使用*符号声明了一个指针变量i_pointer 
	int* i_pointer; 
	// &i的意思是取变量i的地址,那么这句就是将i变量的地址赋值给指针变量i_pointer 
	i_pointer=&i;
	// 那么i_pointer的值就是变量i的地址,下面打印的也是i的地址 
	printf("变量i_pointer的值为:%d\n", i_pointer);
	// 通过&i_pointer可以打印指针变量i_pointer的地址 
	printf("变量i_pointer的地址为:%d", &i_pointer); 
} 

img

10.1.4 认识操作符*&

  • *:取值操作符
  • &:取址操作符
#include 

void main(){
	// 声明一个普通变量 
	int i=2000;
	// 声明一个指针变量,这里的*不是取值操作符使用 
	int *pointer;
	pointer=&i;// 将变量i的地址赋给指针变量pointer作为值
	printf("指针变量pointer所指向变量的值为:%d\n", *pointer); 
	
	// 其他输出,通过这些输出更能深刻体会指针变量 
	printf("普通变量i的值为:%d\n", i);
	printf("普通变量i的地址为:%d\n", &i);
	// printf("普通变量i的值为:%d\n", *i);// 该句代码运行会报错,因为变量i只是一个普通变量,不能使用取值操作符*。 
	printf("指针变量pointer的值为:%d\n", pointer);// 由于pointer是一个指针变量,所以使用普通变量的打印值方式输出的就是变量i的地址
	printf("指针变量pointer的地址为:%d\n", &pointer);// 无论是普通变量还是指针变量都可以通过&符号取该变量的地址 
}

《C语言程序设计》读书笔记(第10章——指针)_第5张图片

代码解释:

  • 如果要声明一个指针变量,必须要使用*标识符,例如int* pointer。而int pointer就表示的是一个普通变量。
  • &是一个取址操作符,取的就是变量的地址,无论是普通变量还是指针变量都可以取地址,例如上面代码中的&i&pointer
  • 如果要输出变量的值,都可以直接使用变量名即可,无论是指针变量还是普通变量,例如上面代码中的ipointer
  • 但如果要输出指针变量所指向变量的值,那么就需要用到了取值操作符*了,例如上面代码中的*pointer
  • 注意,普通变量不能使用*取值操作符,例如上面代码中的*i,那么运行编译会报错。
#include 

int main() {
    int num = 123;// 普通变量
    int *p = #// 指针变量

    printf("%d\n", num);// 123 普通变量的值
    printf("%d\n\n", p);// 6487580 指针变量的值,指针变量存储的是普通变量num的内存地址,注意不同编译器打印出来的地址不一样

    printf("%d\n", &num);// 6487580 普通变量的地址,等于指针变量p的值
    printf("%d\n\n", &p);// 6487568 指针变量的地址

    // printf("%d\n", *num);// 编译报错,即无法通过*取值运算符取出普通变量的值
    printf("%d\n", *p);// 123 指针变量所指向变量的值,即普通变量num的值,可以通过*取值运算符取出指针变量所指向变量的值
}

10.2 变量的指针和指向变量的指针变量

指针与指针变量是有区别的,如果知道了一个变量的地址(内存中的地址),那么就可以通过这个地址来访问这个变量了,因此,就把变量的地址称为该变量的“指针”。

C语言中可以声明一类特殊的变量,这类变量专门用来存放普通变量的地址,称为指针变量。

注意:指针变量的值(即指针变量中存放的值)是普通变量的地址(即指针)。

严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。

10.2.1 定义一个指针变量

对指针变量的定义包括三个内容:

  • 指针类型说明,即定义变量为一个指针变量;
  • 指针变量名;
  • 变量值(指针)所指向的变量的数据类型。

基本语法是:

// 定义指针变量之后赋值
类型说明符* 变量名;
变量名=&普通变量;

// 定义指针变量的同时并赋值
类型说明符* 变量名=&普通变量;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。下面都是合法的指针变量定义,例如:

// pointer是指向float型变量的指针变量
float* pointer;

// c是指向字符型变量的指针变量
char* c;

可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。

char a='A';
// 定义指针变量
char* c;
// 给指针变量赋值
c=&a;

《C语言程序设计》读书笔记(第10章——指针)_第6张图片!

10.2.2 在定义指针变量时需要注意的情况

10.2.2.1 指针变量前面的*表示该变量的类型为指针型变量

定义指针变量的基本语法:

类型说明符* 变量名;
// 还可以写成以下形式,星号在任意靠近谁都可以
类型说明符 * 变量名;
类型说明符 *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。例如:float *pointer_1。注意,指针变量名是pointer_1,而不是*pointer_1

10.2.2.2 在定义指针变量时必须指定基类型

需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。所以下面的赋值是错误的:

float a;
int *pointer_1;
pointer_1=&a;
/* 将float型变量的地址放到指向整型变量的指针变量中,是错误的 */

10.2.3 指针变量的引用

指针变量使用前不仅需要定义说明,而且必须赋予具体的值,未经赋值的指针变量(如int* a;中的a)不能使用,否则将造成系统混乱,甚至死机。

指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。如int* a=123;是错误的。

在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址,所以需要通过某些方式来获取变量的具体地址。

注意:指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量,否则编译器也会把该值当成一个地址来处理。

因此C语言中提供了地址运算符&取变量的地址。基本语法如下:

&变量名;

例如&a表示变量a的地址,&b表示变量b的地址,当然,变量本身是需要预先声明的。

指针变量初始化的语法如下:

// 定义指针变量之后赋值
类型说明符* 变量名;
变量名=&普通变量;

// 定义指针变量的同时并赋值
类型说明符* 变量名=&普通变量;

例如:

int a=123;

// 定义指针变量之后再赋值
int* p;
p=&a;

// 定义指针变量的同时并赋值
int* p=&a;

// 注意,不允许将一个数值直接赋予给指针变量,因此下面的赋值是错误的
int* p=1000;

实例:

#include 

int main() {
    /* 定义普通变量和指针变量 */
    int a, b;// 普通变量a和b
    int* pointer_1;
    int* pointer_2;// 指针变量pointer_1和pointer_2

    /* 为变量赋值 */
    a = 100;
    b = 10;
    pointer_1 = &a;
    pointer_2 = &b;

    /* 打印变量的值 */
    printf("%d, %d\n", a, b);// 100, 10
    printf("%d, %d\n", *pointer_1, *pointer_2);// 100, 10
}

《C语言程序设计》读书笔记(第10章——指针)_第7张图片

10.2.4 对&*运算符的说明

如果已经执行了语句pointer_1=&a;

10.2.4.1 &*pointer_1的含义是什么?

#include 

int main() {
    // 定义变量并且赋值
    int a = 5;
    int* pointer_1 = &a;

    // 打印 &*pointer_1
    printf("&a=%d\n", &a);// &a=6487572
    printf("&*pointer_1=%d", &*pointer_1);// &*pointer_1=6487572
}

会发现&a&*pointer_1的结果一样,都是普通整型变量a的地址。为什么呢?

&*两个运算符的优先级别相同,但按自右而左方向结合,因此先进行*pointer_1的运算,它运算结果是变量a,再执行&运算,就是对变量a取址。即

&*pointer_1
    =&(*pointer_1)
    =&(a)
    =&a

因此&\*pointer_1&a相同,即变量a的地址。

如果有pointer_2=&*pointer_1;,则它的作用是将&a(a的地址)赋给pointer_2,如果pointer_2原来指向b,经过重新赋值后它已不再指向b了,而指向了a

#include 

int main() {
    int a = 5, b = 10;
    int* pointer_1 = &a;
    int* pointer_2 = &b;

    printf("&a=%d\n", &a);// &a=6487564 打印普通变量a的地址
    printf("&*pointer_1=%d\n", &*pointer_1);// &*pointer_1=6487564 其中&*pointer_1=&(*pointer_1)=&(a)=&a,即普通变量a的地址
    printf("&b=%d\n", &b);// &b=6487560 打印普通变量b的地址
    printf("&*pointer_2=%d\n", &*pointer_2);// &*pointer_2=6487560 其中&*pointer_2=&(*pointer_2)=&(b)=&b,即普通变量b的地址

    // 重新赋值
    pointer_2 = &*pointer_1;// 其中&*pointer_1=&(*pointer_1)=&(a)=&a,所以pointer_2 = &*pointer_1 = &a

    printf("&*pointer_2=%d\n", &*pointer_2);// &*pointer_2=6487564 所以最后打印的是变量a的地址
}

《C语言程序设计》读书笔记(第10章——指针)_第8张图片

10.2.4.2 *&a的含义是什么?

&*两个运算符的优先级别相同,但按自右而左方向结合,因此先进行&a运算,得到变量a的地址,再进行*运算,即&a所指向的变量,也就是变量a。即过程如下:

*&a
    =*(&a)
    =*(变量a的地址)
    =a

因此*&a*pointer_1的作用是一样的,它们都等价于变量a

*&aa*pointer_1等价。

#include 

int main() {
    int a = 5;
    int* pointer_1 = &a;

    printf("a=%d\n", a);// a=5
    printf("*pointer_1=%d\n", *pointer_1);// *pointer_1=5
    printf("*&a=%d\n", *&a);// *&a=5
}

10.2.4.3 (*pointer_1)++相当于a++

注意,括号是必要的,如果没有了括号,就成为了*pointer_1++。其中++*为同一优先级别,而结合方向是自右向左的,因此它就相当于*(pointer_1++)

由于++pointer_1的右侧,是“后加”,因此先对pointer_1的原值进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

#include 

int main() {
    int a = 5;
    int* pointer_1 = &a;

    printf("a=%d\n", a);// a=5
    printf("*pointer_1=%d\n", *pointer_1);// *pointer_1=5
    printf("a++=%d\n", a++);// a++=5 本语句执行完成后a的值已经变成了6
    printf("(*pointer_1)++=%d\n", (*pointer_1)++);// (*pointer_1)++=6 所以这里输出的值为6
    printf("a=%d\n", a);// a=7 又进行一个a++运算,所以最终a的值为7
}

10.2.4.4 p2=p1;*p2=*p1;是什么含义(其中p1p2都是指针变量)

赋值表达式p2=p1;

使得p2p1指向同一对象i,此时*p2就等价于i,而不是j,如图所示:

《C语言程序设计》读书笔记(第10章——指针)_第9张图片

代码演示:

#include 

int main() {
    char i = 'a', j = 'b';
    char* p1 = &i;
    char* p2 = &j;

    printf("*p1=%c, p1=%d\n", *p1, p1);// *p1=a, p1=6487567 打印普通变量i的值和地址
    printf("*p2=%c, p2=%d\n", *p2, p2);// *p2=b, p2=6487566 打印普通变量j的值和地址

    // 赋值,值和地址都会改变
    p2 = p1;// p1=&i,所以p2=p1=&i
    printf("\np2=p1;\n\n");

    printf("*p1=%c, p1=%d\n", *p1, p1);// *p1=a, p1=6487567 打印普通变量i的值和地址
    printf("*p2=%c, p2=%d\n", *p2, p2);// *p2=a, p2=6487567 打印普通变量i的值和地址
}

赋值表达式*p2=*p1;

则表示把p1指向的内容赋给p2所指的区域,此时就变成如图所示:

《C语言程序设计》读书笔记(第10章——指针)_第10张图片

代码演示:

#include 

int main() {
    char i = 'a', j = 'b';
    char* p1 = &i;
    char* p2 = &j;

    printf("*p1=%c, p1=%d\n", *p1, p1);// *p1=a, p1=6487567 打印普通变量a的值和地址
    printf("*p2=%c, p2=%d\n", *p2, p2);// *p2=b, p2=6487566 打印普通变量b的值和地址

    // 赋值,值改变,地址没有变
    *p2 = *p1;// 将p2指针变量的值修改为普通变量i的值,但实际上没有修改指针变量p2的地址
    printf("\n*p2=*p1;\n\n");

    printf("*p1=%c, p1=%d\n", *p1, p1);// *p1=a, p1=6487567 
    printf("*p2=%c, p2=%d\n", *p2, p2);// *p2=a, p2=6487566 可以发现指针变量p2的值变了,但地址没有变
}

过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,而且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向),例如*p2=*p1;实际上就是j=i;,前者不仅速度慢而且目的不明。但由于指针是变量,我们可以通过改变它们的指向,以间接访问不同的变量,这给程序员带来灵活性,也使程序代码编写得更为简洁和有效。

指针变量可出现在表达式中,设:

int x,y;
int* px=&x;

指针变量px指向整数x,则*px可出现在x能出现的任何地方。例如:

y=*px+5;  /*表示把x的内容加5并赋给y*/

y=++*px;  /*px的内容加上1之后赋给y,++*px相当于++(*px)*/

y=*px++;  /*相当于y=*px; px++*/ 

因此有如下总结

  • a表示普通变量的值,&a表示普通变量a在内存中地址。
  • p表示指针变量的值,该值是指针变量所指向变量a的地址,*p表示指针变量所指向变量a的值,&p表示指针变量的地址。
#include 

int main() {
    int a = 10;
    int* p = &a;

    printf("a=%d &a=%d\n", a, &a);// a=10 &a=6487580 其中a是普通整型变量a的值,而&a是普通整型变量a的地址
    printf("p=%d *p=%d &p=%d\n", p, *p, &p);// p=6487580 *p=10 &p=6487568 其中p是指针变量a所指向变量a的地址,而*p是指针变量所指向变量的值,而&p是指针变量p的地址
}

10.2.5 指针练习

10.2.5.1 输入a和b两个整数,按先大后小的顺序输出a和b

需求:输入a和b两个整数,按先大后小的顺序输出a和b。

#include 

int main() {
    int a, b;

    printf("请输入任意两个整数:\n");
    scanf("%d%d", &a, &b);

    if (a > b) {
        printf("%d %d", a, b);
    } else {
        printf("%d %d", b, a);
    }
}

使用指针解决:

#include 

/**
 * 题目:输入a和b两个整数,按先大后小的顺序输出a和b
 */
int main() {
    // 声明a和b两个普通变量,max和min三个指针变量
    int a, b;
    int* max;
    int* min;
    int temp;

    // 从键盘输入获取输入的a和b两个整数,并将a和b的地址赋值给指针变量
    printf("请输入任意两个整数:\n");
    scanf("%d%d", &a, &b);
    max = &a;
    min = &b;

    // 进行判断处理
    if (a < b) {// 交换两个指针变量的值,temp充当交换的媒介 
        temp = *max;
        *max = *min;
        *min = temp;
    }
    printf("%d %d", *max, *min);
}

打印结果如下:

请输入任意两个整数:
12 34
34 12

《C语言程序设计》读书笔记(第10章——指针)_第11张图片

10.2.5.2 输入a、b、c三个整数,按大小顺序输出

思路是这样的:a先和b比较大小,如果a大于b则不变,如果a小于b则交换它们的值,然后仍然是a大于b,此时a和b之间a是最大值,那么此时只需要比较a和c的大小了,如果a大于c则a是三个数中的最大值,此时只需要比较b和c的大小就可以得到顺序了,如果a小于c则交换它们的值,然后仍然是a大于c,此时a和b和c之间a是最大值;最后只需要比较b和c之间谁大就可以了。

#include 

/**
 * 题目:输入a、b、c三个整数,按大小顺序输出。
 */

/**
 * 交换两个指针变量的值
 * @param x
 * @param y
 */
void swap(int* x, int* y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    // 声明a和b两个普通变量,p1、p2和p3三个指针变量
    int a, b, c;
    int *p1;
    int *p2;
    int *p3;

    // 从键盘输入获取输入的a、b和c,并将a、b和c的地址赋值给指针变量
    printf("请输入三个整数:");
    scanf("%d%d%d", &a, &b, &c);
    p1 = &a;
    p2 = &b;
    p3 = &c;

    // 进行判断处理
    if (*p1 < *p2) {// 如果p2大于p1,则交换p1与p2的值,那么p1>p2了
        swap(p1, p2);
    }
    if (*p1 < *p3) {// 如果p3大于p1,则交换p3与p1的值,那么p1>p3了
        swap(p1, p3);
    }
    if (*p2 < *p3) {// 如果p3大于p2,则交换p3与p2的值,那么p2>p3了
        swap(p2, p3);
    }

    // 打印结果
    printf("%d %d %d\n", a, b, c);
}

10.2.6 指针变量作为函数参数

函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

#include 

// 交换两个数值 
void swap(int* p1, int* p2) {// 指针变量作为形参 
    int temp;// 临时变量,保存另一个变量的值 
    temp = *p1;// *p1,*p2表示取该指针变量所指向变量的值 
    *p1 = *p2;
    *p2 = temp;
}

int main() {
    int a, b;
    int *pointer_1, *pointer_2;

    scanf("%d, %d", &a, &b);
    pointer_1 = &a;
    pointer_2 = &b;

    if (a < b) {
        swap(pointer_1, pointer_2);// 注意调用函数时,传的是指针变量名,没有带有*号 
    }

    printf("\n%d, %d\n", a, b);
} 

《C语言程序设计》读书笔记(第10章——指针)_第12张图片

代码解释:swap是用户定义的函数,它的作用是交换两个变量(a和b)的值。swap函数的形参p1、p2是指针变量。程序运行时,先执行main函数,输入a和b的值。然后将a和b的地址分别赋给指针变量pointer_1和pointer_2,使pointer_1指向a,pointer_2指向b。

《C语言程序设计》读书笔记(第10章——指针)_第13张图片

接着执行if语句,由于a*pointer_1和*pointer_2取到a和b的值了)方式。因此虚实结合后形参p1的值为&a,p2的值为&b。这时p1和pointer_1指向变量a,p2和pointer_2指向变量b。

《C语言程序设计》读书笔记(第10章——指针)_第14张图片

接着执行执行swap函数的函数体使*p1*p2的值互换,也就是使a和b的值互换。

《C语言程序设计》读书笔记(第10章——指针)_第15张图片

函数调用结束后,p1和p2不复存在(已释放)如图。

《C语言程序设计》读书笔记(第10章——指针)_第16张图片

最后在main函数中输出的a和b的值是已经过交换的值。

下面这段代码有错误:

// 交换两个数值 
swap(int* p1, int* p2){
	int* temp;
	*temp=*p1;// 此语句有问题
	*p1=*p2;
	*p2=temp;
}

因为*p1表示所指向变量的值,即a的值,不是地址,而给指针变量temp赋值应该赋予一个地址。还可以看看如下错误代码:

#include 

int main() {
    int* a;
    *a = 123;
    printf("a=%d\n", a);
    printf("*a=%d\n", *a);
    printf("&a=%d\n", &a);
}

10.2.7 指针变量几个问题的进一步说明

指针变量可以进行某些运算,但其运算的种类是有限的。它只能进行赋值运算和部分算术运算及关系运算。

10.2.7.1 指针运算符

  • 取地址运算符&:取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。
  • 取内容运算符*:取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量的值。在*运算符之后跟的变量必须是指针变量。

需要注意的是指针运算符*和指针变量说明中的指针说明符*不是一回事。在指针变量说明中,*是类型说明符,表示其后的变量是指针类型。而表达式中出现的*则是一个运算符用以表示指针变量所指的变量。

#include 

void main() {
    int a = 5;
    // 表示指针变量p取得了整型变量a的地址
    int* p;
    p = &a;

    // printf("%d", *p)语句表示输出变量a的值
    printf("%d", *p);// 5
}

10.2.7.2 指针变量的运算

10.2.7.2.1 赋值运算

指针变量的赋值运算有如下几种形式:

  • 指针变量初始化赋值
int a = 5;
int* p = &a;// 在初始化的同时并赋值
  • 把一个变量的地址赋予指向相同数据类型的指针变量
int a;
int* p;
p = &a;// 定义指针变量后再赋值
  • 把一个指针变量的值赋予指向相同类型变量的另一个指针变量
int a;
int* p1 = &a;
int* p2;
p2 = p1;// 等价于p2=p1=&a;,即把a的地址赋予指针变量p2
  • 把数组的首地址赋予指向数组的指针变量
int a[5];// 定义普通整型数组
int* p;// 定义整型指针变量
p = a;// 在定义之后再赋值,数组名表示数组的首地址,因此可以赋予指向数组的指针变量p

// 还可以写成这样:
p = &a[0];// 数组的第一个元素的地址就是整个数组的首地址,又可以赋值给p

// 还可以写成这样:
int a[5];
int* p = a;// 在定义的同时并赋值
  • 把字符串的首地址赋予指向字符类型的指针变量
char* p;
p = "I love C!";// 定义一个字符指针变量,值为一个字符串

// 还可以写成这样:在定义指针变量的同时并赋值
char* p = "I love C!";// 这里应说明的是并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。
  • 把函数入口地址赋予指向函数的指针变量
int (*pf)();
pf = f;// f为函数名
10.2.7.2.2 加减算术运算

对于指向数组的指针变量,可以加上或减去一个整数n。

设pa是指向数组a的指针变量,则pa+npa-npa++++papa----pa运算都是合法的。

指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置

应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。例如:

int a[5];
int* pa;
pa=a; // pa指向数组a,也是指向a[0]
pa=pa+2; // pa指向a[2],即pa的值为&pa[2]

指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。

10.2.7.2.3 指针变量之间的运算

两个指针变量之间的运算:只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。

  • 两指针变量相减:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
#include 

int main() {
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int* p1 = a;
    int* p2 = a + 6;

    printf("p1=%d *p1=%d\n", p1, *p1);// p1=6487520 *p1=1
    printf("p2=%d *p2=%d\n", p2, *p2);// p2=6487544 *p2=7

    // 两指针变量相减
    printf("%d", p2 - p1);// 6 相差6个元素
}
  • 两个指针变量不能进行加法运算。 例如,p1+p2是什么意思呢?毫无实际意义。

  • 两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。

pf1==pf2 // 表示pf1和pf2是否指向同一数组元素
pf1>pf2 // 表示pf1处于高地址位置
pf1<pf2 // 表示pf2处于低地址位置

// 指针变量还可以和0比较
p==0 // 表示p是空指针,它不指向任何变量。空指针是由对指针变量赋予0值而得到的。
p!=0 // 表示p不是空指针
// 对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。

例如:

#include 

void main() {
    int s, t;
    int a = 10, b = 20;
    int *pa, *pb; // 说明pa,pb为整型指针变量

    pa = &a; // 给指针变量pa赋值,pa指向变量a
    pb = &b; // 给指针变量pb赋值,pb指向变量b

    s = *pa + *pb; // 求a+b之和,(*pa就是a,*pb就是b)
    t = *pa * *pb; // 本行是求a*b之积

    printf("a=%d\tb=%d\ta+b=%d\ta*b=%d\n", a, b, a + b, a * b);// a=10    b=20    a+b=30  a*b=200
    printf("s=%d\tt=%d\n", s, t);// s=30    t=200
}

例如:

#include 

void main() {

    int a, b, c;
    int *pmax, *pmin;// pmax,pmin为整型指针变量

    printf("input three numbers:\n");// 输入提示
    scanf("%d%d%d", &a, &b, &c);// 输入三个数字

    // 先比较a和b的大小,确定最大值和最小值
    if (a > b) {      // 如果第一个数字大于第二个数字
        pmax = &a;// 指针变量赋值
        pmin = &b;// 指针变量赋值
    } else {// 如果第一个数字不大于第二个数字
        pmax = &b;
        pmin = &a;
    }

    // 再比较最大值和c或者最小值和c的大小,用来确定第三个的大小
    if (c > *pmax) pmax = &c;
    if (c < *pmin) pmin = &c;

    printf("max=%d\tmin=%d\n", *pmax, *pmin);
}

10.3 数组指针和指向数组的指针变量

一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元。一个数组元素的首地址也是指它所占有的几个内存单元的首地址。

一个变量有对应的内存单元地址,一个数组包含若干元素,每个数组元素都在内存中占有存储单元,它们都有相应的地址。

指针变量既可以指向变量,也可以指向数组元素(把某一元素的地址放到指针变量中)。

所谓数组元素的指针就是数组元素的地址。

10.3.1 指向数组元素的指针

《C语言程序设计》读书笔记(第10章——指针)_第17张图片!

定义一个指向数组元素的指针变量的方法,与之前介绍的指向变量的指针变量相同,有如下两种方式:

int a[10];// 定义一个整型数组,包含10个元素

// 第一种方式,将数组的第一个元素的地址赋给指针变量
int* p;// 定义一个指向整型变量的指针变量
p = &a[0];// 对指针变量进行赋值,是定义变量之后再赋值。把a[0]元素的地址赋给指针变量p。也就是说,p指向a数组的第0号元素。

int* p = &a[0];// 定义指针变量的同时就赋予初值

// 第二种方式,将数组名赋给指针变量
int* p;
p = a;// 也可以这样赋值。因为C语言规定,数组名代表数组的首地址,也就是第0号元素的地址。

int* p = a;// 定义指针变量的同时就赋予初值

数组指针变量说明的一般形式为:

类型说明符* 指针变量名;

其中类型说明符表示所指数组的类型。从一般形式可以看出指向数组的指针变量和指向普通变量的指针变量的说明是相同的。

10.3.2 通过指针引用数组元素

C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。

如果p的初值为&a[0],则:

  • p+ia+i就是a[i]的地址,或者说它们指向a数组的第i个元素。p+i=a+i=&a[i]
  • *(p+i)*(a+i)就是p+ia+i所指向的数组元素,即a[i]。例如,*(p+5)*(a+5)就是a[5]*(p+i)=*(a+i)=a[i]
  • 指向数组的指针变量也可以带下标,如p[i]*(p+i)等价。*(p+i)=*(a+i)=a[i]=p[i]
#include 

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

    int* p = &a[0];// 定义指针变量p的同时并赋予初值

    // 设置i等于5
    int i = 5;
    printf("p+i=%d\ta+i=%d\t&a[i]=%d\n", (p + i), (a + i), &a[i]);// p+i=6487556     a+i=6487556     &a[i]=6487556
    printf("*(p+i)=%d\t*(a+i)=%d\ta[i]=%d\n", *(p + i), *(a + i), a[i]);// *(p+i)=6        *(a+i)=6        a[i]=6
    printf("p[i]=%d\ta[i]=%d\t*(p+i)=%d\t*(a+i)=%d", p[i], a[i], *(p + i), *(a + i));// p[i]=6  a[i]=6  *(p+i)=6        *(a+i)=6
}

《C语言程序设计》读书笔记(第10章——指针)_第18张图片

引入指针变量后,就可以用两种方法来访问数组元素了。故引用一个数组元素,可以用:

  • 下标法,例如a[i]p[i]形式
  • 指针法,如*(a+i)*(p+i)形式

其中a是数组名,p是指向数组元素的指针变量,其初值为p=ap=&a[0]

注意:数组名即“翻译成数组的第一个元素的地址”

数组名表示数组的第一个元素的地址,而数组名加i(例如a+i)表示数组中第i各元素的地址,可以通过"*"(例如***(a+i)**)来获取该地址对应元素的值。

#include 

void main() {
    // 一维数组
    int a[10]= {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // 打印一维数组
    int i;
    for(i=0; i<10; i++) {
        printf("%d\t",a[i]);
    }
    printf("\n");

    // 指针的应用
    for(i=0; i<10; i++) {
        printf("a+%d = %d\t*(a+%d) = %d\n", i, a+i, i, *(a+i));
    }
}

《C语言程序设计》读书笔记(第10章——指针)_第19张图片

例如,输出数组中的全部元素。假设有一个a数组,整型,有10个元素,输出数组各元素22有如下几种方法:

  • ①下标法:a[i]
#include 

void main() {
    // 定义一个整型数组
    int a[10];

    // 从键盘输入赋值数组元素
    for (int i = 0; i < 10; i++) {
        scanf("%d", &a[i]);
    }

    // 换行
    printf("\n");

    // 通过数组下标输出数组全部元素
    for (int i = 0; i < 10; i++) {
        printf("%d\t", a[i]);
    }
}
  • ②通过数组名计算数组元素的地址找出元素的值:*(a+i)
#include 

void main() {
    // 定义一个整型数组
    int a[10];

    // 从键盘输入赋值数组元素
    for (int i = 0; i < 10; i++) {
        scanf("%d", &a[i]);
    }

    // 换行
    printf("\n");

    // 通过数组名计算数组元素的地址,找出元素的值
    for (int i = 0; i < 10; i++) {
        printf("%d\t", *(a + i));
    }
}
  • ③用指针变量指向数组元素:*(p+i)
#include 

void main() {
    // 定义一个整型数组
    int a[10];
    int* p = a;

    // 从键盘输入赋值数组元素
    for (int i = 0; i < 10; i++) {
        scanf("%d", &a[i]);
    }

    // 换行
    printf("\n");

    // 通过指针变量指向元素,输出数组的全部元素
    for (int i = 0; i < 10; i++) {
        printf("%d\t", *(p + i));
    }
}
  • ④用指针变量下标访问数组元素:p[i]
#include 

void main() {
    // 定义一个整型数组
    int a[10];
    int* p = a;

    // 从键盘输入赋值数组元素
    for (int i = 0; i < 10; i++) {
        scanf("%d", &a[i]);
    }

    // 换行
    printf("\n");

    // 通过指针变量指向元素,输出数组的全部元素
    for (int i = 0; i < 10; i++) {
        printf("%d\t", p[i]);
    }
}

几个注意的问题:

  • 指针变量可以实现本身的值的改变。如p++是合法的;而a++是错误的。因为a是数组名,它是数组的首地址,是常量。
  • 要注意指针变量的当前值。

这是一段错误的代码:

#include 

void main() {
	int *p,i,a[10];
	p=a;

	for(i=0; i<10; i++)
	{
		*p++=i;
	}

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

《C语言程序设计》读书笔记(第10章——指针)_第20张图片

改正:

#include 

void main() {
	int *p,i,a[10];
	p=a;

	for(i=0; i<10; i++)
	{
		*p++=i;
	}
	
	p=a;// 将指针重新指向数组首元素地址 
	
	for(i=0; i<10; i++)
	{
		printf("a[%d]=%d\n",i,*p++);
	}
}

《C语言程序设计》读书笔记(第10章——指针)_第21张图片

代码解释:

  • 虽然定义数组时指定它包含10个元素,但指针变量可以指到数组以后的内存单元,系统并不认为非法。
  • *p++,由于++*同优先级,结合方向自右而左,等价于*(p++)
  • *(p++)*(++p)作用不同。若p的初值为a,则*(p++)等价a[0]*(++p)等价a[1]
  • (*p)++表示p所指向的元素值加1。
  • 如果p当前指向a数组中的第i个元素,则
    • *(p--)相当于a[i--]
    • *(++p)相当于a[++i]
    • *(--p)相当于a[--i]

10.3.3 数组名作函数参数

数组名可以作函数的实参和形参。基本形式如下:

void fun(int arr[], int length) {
    ...
}

// 或者

void fun(int* arr, int length) {
    ...
}

int main(){
    int array[10];
    ...
    fun(arr, 10);    
}

f(int arr[ ], int n)在编译时将arr按指针变量处理的,相当于将函数f的首部写成f(int *arr, int n)。这两种写法是等价的。

数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同一数组。

注意:C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数传递的是变量的值;但用数组名作为函数参数时,由于数组名代表的是数组首元素的地址,因此传递的值是地址,所以要求形参为指针变量。

因此,如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下四种情况:

  • 第一种情况:形参和实参都用数组名,如:
void fun(int a[], int length) {
    // 可以通过数组下标和指针的方式访问修改数组中的元素
    ...
}

void main() {
    int a[10];
    ...
    fun(a, 10);    
}

实例:

#include 

// 数组名作为函数的实参和形参
void fun(int arr[], int length) {
    for (int i = 0; i < length; i++) {
        // 可以使用指针的方式
        // *(arr+i) = *(arr+i) * 10;
        // 也可以使用数组下标的方式
        arr[i] = arr[i] * 10;
    }
}

void main() {
    int nums[] = {1, 2, 3, 4, 5};
    int length = 5;

    // 打印调用函数之前的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 1       2       3       4       5
    printf("\n");

    // 调用函数
    fun(nums, length);

    // 打印调用函数之后的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 10      20      30      40      50
}
  • 第二种情况:实参用数组名,形参用指针变量。如:
void fun(int* a, int length) {
    // 可以通过数组下标和指针的方式访问修改数组中的元素
    ...
}

void main() {
    int a[10];
    ...
    fun(a, 10);    
}

实例:

#include 

// 函数实参用数组名,形参用指针变量
void fun(int* arr, int length) {
    for (int i = 0; i < length; i++) {
        // 可以使用指针的方式
        // *(arr+i) = *(arr+i) * 10;
        // 也可以使用数组下标的方式
        arr[i] = arr[i] * 10;
    }
}

void main() {
    int nums[] = {1, 2, 3, 4, 5};
    int length = 5;

    // 打印调用函数之前的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 1       2       3       4       5
    printf("\n");

    // 调用函数
    fun(nums, length);

    // 打印调用函数之后的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 10      20      30      40      50
}
  • 第三种情况:实参和形参都用指针变量。如:
void fun(int* a, int length) {
	// 可以通过数组下标和指针的方式访问修改数组中的元素
    ...
}

void main() {
    int a[10];
    int* p = a;
    ...
    fun(p, 10);    
}

实例:

#include 

// 实参和形参都用指针变量
void fun(int* arr, int length) {
    for (int i = 0; i < length; i++) {
        // 可以使用指针的方式
        // *(arr+i) = *(arr+i) * 10;
        // 也可以使用数组下标的方式
        arr[i] = arr[i] * 10;
    }
}

void main() {
    int nums[] = {1, 2, 3, 4, 5};
    int length = 5;
    int* p = nums;

    // 打印调用函数之前的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 1       2       3       4       5
    printf("\n");

    // 调用函数
    fun(p, length);

    // 打印调用函数之后的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 10      20      30      40      50
}
  • 第四种情况:实参用指针变量,形参用数组名。如:
void fun(int a[], int length) {
    // 可以通过数组下标和指针的方式访问修改数组中的元素
    ...
}

void main() {
    int a[10];
    int* p = a;
    ...
    fun(p, 10);    
}

实例:

#include 

// 实参为指针变量,形参为数组名
void fun(int arr[], int length) {
    for (int i = 0; i < length; i++) {
        // 可以使用指针的方式
         *(arr+i) = *(arr+i) * 10;
        // 也可以使用数组下标的方式
        // arr[i] = arr[i] * 10;
    }
}

void main() {
    int nums[] = {1, 2, 3, 4, 5};
    int length = 5;
    int* p = nums;

    // 打印调用函数之前的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 1       2       3       4       5
    printf("\n");

    // 调用函数
    fun(p, length);

    // 打印调用函数之后的数组
    for (int i = 0; i < length; i++) {
        printf("%d\t", nums[i]);
    }// 10      20      30      40      50
}

总结:无论是上面四种的哪一种情况,都可以通过数组下标或者指针的方式访问修改数组元素

10.3.4 指向多维数组的指针和指针变量

用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。

但在概念上和使用上,多维数组的指针比一维数组的指针要复杂。

多维数组元素的地址可以认为是二维数组是“数组的数组”,例:

int a[3][4] = {
    {1,3,5,7},
    {9,11,13,15},
    {17,19,21,23}
}

则二维数组a是由3各一维数组所组成的。设二维数组的首行的首地址为2000,则有:

《C语言程序设计》读书笔记(第10章——指针)_第22张图片

《C语言程序设计》读书笔记(第10章——指针)_第23张图片

看下面的代码:

#include 

void main() {
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	
	// 二维数组名,指向一维数组a[0],即0行首地址 
	printf("a = %d\n", a);
	
	// 其中a[0],*(a+0),*a都是等价的,代表第0行第0列元素的地址 
	printf("a[0] = %d  *(a+0) = %d  *a = %d\n", a[0], *(a+0), *a);
	
	// 其中a+1, &a[1]是等价的,代表1行的首地址
	printf("a+1 = %d  &a[1] = %d\n", a+1, &a[1]);
	
	// 其中a[1], *(a+1)是等价的,代表1行0列元素a[1][0]的地址
	printf("a[1] = %d  *(a+1) = %d\n", a[1], *(a+1));
	
	// 其中a[1]+2, *(a+1)+2, &a[1][2]是等价的,代表1行2列元素a[1][2]的地址
	printf("a[1]+2 = %d  *(a+1)+2 = %d  &a[1][2] = %d\n", a[1]+2, *(a+1)+2, &a[1][2]);
	
	// 其中*(a[1]+2), *(*(a+1)+2), a[1][2]是等价的,代表1行2列元素a[1][2]的元素值
	printf("*(a[1]+2) = %d  *(*(a+1)+2) = %d  a[1][2]=%d", *(a[1]+2), *(*(a+1)+2), a[1][2]); 
}

《C语言程序设计》读书笔记(第10章——指针)_第24张图片

10.3.5 总结

1、在数组中,数组名表示首地址,即第0行第0列的元素的地址,无论是一维数组还是多维数组。

#include 

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n");
	
	printf("a=%d, b=%d", a, b);
} 

《C语言程序设计》读书笔记(第10章——指针)_第25张图片

2、数组名也表示二维数组中第一(下标是0)行的首地址,如果加1表示第二(下标是1)行的首地址,依次如此。

#include 

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	
	printf("a = %d\n", a);// 二维数组第1行(行下标为0)首地址 
	printf("a+1 = %d\n", a+1);// 二维数组第2行(行下标为1)首地址 
	printf("a+2 = %d\n", a+2);// 二维数组第3行(行下标为2)首地址 
} 

《C语言程序设计》读书笔记(第10章——指针)_第26张图片

注意:a[0]、a[1]、a[2]等价于a、a+1、a+2,都表示第1行(行下标为0)、第2行、第3行的行首地址。a[i]=a+i

#include 

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	
	printf("a = %d \t a[0] = %d\n", a, a[0]);// 二维数组第1行(行下标为0)首地址 
	printf("a+1 = %d \t a[1] = %d\n", a+1, a[1]);// 二维数组第2行(行下标为1)首地址 
	printf("a+2 = %d \t a[2] = %d\n", a+2, a[2]);// 二维数组第3行(行下标为2)首地址 
} 

《C语言程序设计》读书笔记(第10章——指针)_第27张图片

3、取值操作符"*“和取址操作符”&"的应用*a*(a+1)&a[1]等。

取值操作符"*"就是取指针变量所指向变量的值。

#include 

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	printf("a = %d \t *a = %d \t &a = %d\n", a, *a, &a);
	printf("a+0 = %d \t *(a+0) = %d\n", a+0, *(a+0));// 注意,不能&(a+0),这是非法语句,无法编译,因为a+0的结果放在寄存器,无法取址 
	printf("a+1 = %d \t *(a+1) = %d\n", a+1, *(a+1));// 注意,不能&(a+1) 
	printf("a+2 = %d \t *(a+2) = %d\n", a+2, *(a+2));// 注意,不能&(a+2) 
} 

《C语言程序设计》读书笔记(第10章——指针)_第28张图片

取址操作符是取变量的地址。

#include 

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	printf("a[0] = %d \t &a[0] = %d \t *a[0] = %d\n", a[0], &a[0], *a[0]);
	printf("a[1] = %d \t &a[1] = %d \t *a[1] = %d\n", a[1], &a[1], *a[1]);
	printf("a[2] = %d \t &a[2] = %d \t *a[2] = %d\n", a[2], &a[2], *a[2]);
} 

《C语言程序设计》读书笔记(第10章——指针)_第29张图片[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N88G8iSj-1645450193889)(data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==)]

所以a[0]&a[0]等价,都是地址相等,而*a[0]表示取第1(下标为0)行首元素的值。

4、第2(下标为1)行第3(下标为2)列的地址和值

#include 

void main() {
	// 二维数组 
	int a[3][4]= {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
	// 一维数组 
	int b[3]={1,2,3}; 
	
	// 打印二维数组 
	printf("二维数组:\n");
	int i,j;
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("a[%d][%d]=%d,%d\t", i, j, a[i][j], &a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	// 打印一维数组
	printf("一维数组:\n");
	int m;
	for(m=0;m<3;m++){
		printf("b[%d]=%d,%d\t", m, b[m], &b[m]);
	} 
	printf("\n\n");
	
	printf("a[1]+2 = %d \t *(a+1)+2 = %d \t &a[1][2] = %d\n", a[1]+2, *(a+1)+2, &a[1][2]);
	printf("*(a[1]+2) = %d \t *(*(a+1)+2) = %d \t a[1][2] = %d\n", *(a[1]+2), *(*(a+1)+2), a[1][2]);
} 

《C语言程序设计》读书笔记(第10章——指针)_第30张图片

10.3.6 指向多维数组元素的指针变量

把二维数组a分解为一维数组a[0], a[1], a[2]后,设p为指向二维数组的指针变量。

可以定义为:int (*p)[4];

它表示p是一个指针变量,它指向包含4各元素的一维数组。若指向第一个一维数组a[0],其值等于aa[0]&a[0][0]等。

从上面的分析得知:

  • *(p+i)+j是二维数组i行j列的元素的地址。
  • *(*(p+i)+j)是二维数组i行j列元素的值。

二维数组指针变量说明的一般形式为:

类型说明符 (*指针变量名)[长度];

其中“类型说明符”为所指数组的数据类型。“*”表示其后的变量是指针类型。“长度”表示二维数组分解为多个一维数组时,一维数组的长度,也就是二维数组的列数。

#include 

void main(){
	int a[3][4]={0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
	int (*p)[4];
	int i,j;
	p=a;
	
	for(i=0;i<3;i++){
		for(j=0;j<4;j++){
			printf("%2d ", *(*(p+i)+j));
		}
		printf("\n");
	}
}

《C语言程序设计》读书笔记(第10章——指针)_第31张图片

10.4 字符串的指针和指向字符串的指针变量

10.4.1 字符串的表示形式

在C语言中,可以使用两种方法声明一个字符串:

  • 用字符数组存放一个字符串,然后输出该字符串。
#include 

int main() {
    char string[] = "I love C!";
    printf("%s\n", string);
}
  • 用字符指针指向一个字符串。
#include 

int main() {
    char* string = "I love C!";
    printf("%s\n", string);
}

首先定义string是一个字符指针变量,然后把字符串的首地址赋予string(应写出整个字符串,以便编译系统把该串装入连续的一块内存单元),并把首地址送入string。程序中的:

char* p = "I love C!";
// 等效于
char* p;
p = "I love C!";

// 但下面的情况却不能等价,并且连编译都无法通过
char p[10] = "I love C!";
// 不等效于,并且下面的语句无法通过编译
char p[10];
p = "I love C!";

10.4.2 字符串中字符的存取方法

对于字符串中字符的获取或修改,可以采用下标方式,也可以采用指针方式。如下:

  • 下标方式。
#include 

// 将字符串中的大写字母转换成小写字母
int main() {
    char str[] = "Hello World";

    // 使用下标方式访问字符串中的字符
    for (int i = 0; i < 11; i++) {
        // 如果是大写字母则转换小写字母
        if (str[i] >= 65 && str[i] <= 90) {
            str[i] = (char) (str[i] + 32);
        }
    }

    printf("%s", str);
}
  • 指针方式
#include 

// 将字符串中的大写字母转换成小写字母
int main() {
    char str[] = "Hello World";
    char* p = str;

    // 使用下标方式访问字符串中的字符
    for (int i = 0; i < 11; i++) {
        // 如果是大写字母则转换小写字母
        /* 这样用指针变量下标访问修改是可以的 */
//        if (p[i] >= 65 && p[i] <= 90) {
//            p[i] = (char) (p[i] + 32);
//        }
        /* 这样通过指针方式访问修改也是可以的 */
        if (*(p + i) >= 65 && *(p + i) <= 90) {
            *(p + i) = (char) (*(p + i) + 32);
        }
    }

    printf("%s", str);
}

但注意,下面这种情况是不行的:

#include 

// 将字符串中的大写字母转换成小写字母
int main() {
    // 使用指针变量来声明字符串的方式中的字符是不能被修改的
    char* p = "Hello World";
    // 使用数组来字符串的方式中的字符是可以被修改的
    // char p[] = "Hello World";

    // 使用下标方式访问字符串中的字符
    for (int i = 0; i < 11; i++) {
        // 如果是大写字母则转换小写字母
        /* 这样通过下标方式可以访问,但不能修改 */
        if (p[i] >= 65 && p[i] <= 90) {
            p[i] = (char) (p[i] + 32);// 即不能修改
        }
        /* 这样通过指针方式可以访问,但不能修改 */
//        if (*(p + i) >= 65 && *(p + i) <= 90) {
//            *(p + i) = (char) (*(p + i) + 32);// 即不能修改
//        }
    }

    printf("%s", p);
}

10.4.3 字符指针作为函数参数

  • 字符数组作为形参,字符数组作为实参。
#include 

/**
 * 复制字符串
 * @param from 源字符串
 * @param to 目标字符串
 */
void copy(char from[], char to[]) {
    int i = 0;
    while (from[i] != '\0') {
        to[i] = from[i];
        i++;
    }
    to[i] = '\0';
}

int main() {
    char src[] = "I love C!";
    char dest[] = "Hello World!";

    printf("src=%s\tdest=%s\n", src, dest);// src=I love C!   dest=Hello World!

    // 调用函数
    copy(src, dest);// 实参为字符数组,形参为字符数组

    printf("src=%s\tdest=%s\n", src, dest);// src=I love C!   dest=I love C!
}
  • 字符数组作为形参,字符指针作为形参。
#include 

/**
 * 复制字符串
 * @param from 源字符串
 * @param to 目标字符串
 */
void copy(char* from, char* to) {
    int i = 0;
    while (from[i] != '\0') {// 可以通过下标方式访问修改,还可以通过指针方式访问修改
        to[i] = from[i];
        i++;
    }
    to[i] = '\0';
}

int main() {
    // 注意,第一个参数是字符指针,第二个参数是字符数组
    // 但 char* src = "I love C!"; 是有效的
    // 如果 char* dest = "Hello World!"; 是无效的
    char* src = "I love C!";
    char dest[] = "Hello World!";

    printf("src=%s\tdest=%s\n", src, dest);// src=I love C!   dest=Hello World!

    // 调用函数
    copy(src, dest);

    printf("src=%s\tdest=%s\n", src, dest);// src=I love C!   dest=I love C!
}

10.4.4 对使用字符指针变量和字符数组的讨论

虽然用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不能混为一谈。主要概括有如下几点:

  • 1、字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第1个字符的地址),决不是将字符串放到字符指针变量中。
#include 

void main(){
	// 字符串数组
	char a[]="I love C!";
	// 字符指针变量
	char *b="I love C!";
	
	// 打印结果
	int i;
	printf("字符串数组:\n");
	for(i=0;a[i]!='\0';i++){
		printf("%c", a[i]);
	} 
	printf("\n\n");
	
	printf("字符指针变量:\n");
	for(;*b!='\0';b++){// 注意,*b表示值,而b表示指针变量,所以*b!='\0'比较的是字符的值是否到最后一个字符,而b++表示指针向数组的下一个元素移动一个单位 
		printf("%d\t", b);
		// printf("%c", *b);// 打印的指针变量的值 
	}
}

《C语言程序设计》读书笔记(第10章——指针)_第32张图片

  • 2、赋值方式。

对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值:

/* 对于字符数组,正确的赋值方式 */
char str[10] = "hello world!";
// 或者
char str[10];
str[0]='h';
str[1]='e';

/* 对于字符数组,错误的赋值方式 */
char str[10];
str = "hello world";// 错误的

但对于字符指针变量,可以采用下面的方式赋值:

/* 对于字符指针变量,正确的赋值方式 */
char* a = "hello world";
// 或者
char* a;
a="hello world";

但注意赋给a的不是字符,而是字符串第一个元素的地址。

#include 

void main(){
	// 不能使用如下方式对字符数组赋值
	char str[10];
	// error: assignment to expression with array type
	// str="I love C!"; 
	
	// 但对字符指针变量,可以这样赋值
	char *a;
	a="I love C!";
	printf("%d\n", a);// 打印的字符串第一个元素的地址
	printf("%c", *a); // 打印值  
} 

img

  • 3、对字符指针变量和字符数组赋初值

字符指针变量的初始化:

char* a = "hello world";

// 等价于

char* a;
a = "hello world";

字符数组的初始化:

char str[20] = "I love C!";

// 不能等价于

char str[20];
str[] = "I love C!";
  • 4、字符数组和字符指针的地址

如果定义了一个字符数组,在编译时为它方配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量行配内存单元,在其中可以放一个字符变量的地址也就是说,该指针变量可以指向一个字符型数据,如果未对它随予一个地址值,则它并未具体指向一个确定的字符数据。如:

char str[10];
scanf("%s", str);// 正确的

一般有人使用下面的方式,目的想要输入一个字符串,虽然能够运行,但是危险的:

char *a;
scanf("%s", a);// 错误的
  • 5、指针变量的值是可以改变的

例如:

#include 

void main(){
	char *a="I love C!";
	printf("%s\n", a);
	
	a+=3;// 在原第一个元素指针的基础上加上3,表示指针向后移动三个单位 
	printf("%s\n", a); 
}

img

另外需要说明的是,若定义了一个指针变量,并使它指向一个字符串,就可以使用下标形式引用指针变量所指的字符串中的字符。

#include 

void main(){
	char *a="I love C!";
	int i;
	
	printf("The sixth character is %c\n\n", a[5]);
	
	for(i=0; a[i]!='\0'; i++){
		printf("%c", a[i]);
	}
	printf("\n");
} 

《C语言程序设计》读书笔记(第10章——指针)_第33张图片

10.5 函数指针变量

10.5.1 函数指针变量概念

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

函数指针变量定义的一般形式如下:

类型说明符 (*指针变量名)();

其中“类型说明符”表示被指函数的返回值的类型。“(* 指针变量名)”表示“*”后面的变量是定义的指针变量。最后的空括号()表示指针变量所指的是一个函数。例如:

int (*pf)();// 表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。

实例:

#include 

int max(int a, int b) {
    return a > b ? a : b;
}

int main() {
    int a = 10, b = 5;

    // 直接调用函数
    int c1 = max(a, b);
    printf("a=%d\tb=%d\tmax=%d\n", a, b, c1);// a=10    b=5     max=10

    // 通过指针变量来调用函数
    int (*fn)();
    fn = max;
    int c2 = (*fn)(a, b);
    printf("a=%d\tb=%d\tmax=%d\n", a, b, c2);// a=10    b=5     max=10
}

10.5.2 用指向函数的指针作函数参数

函数指针变量常用的用途之一是把指针作为参数传递到其他函数。

函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等,那么指向函数的指针也可以作为参数,来实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。

《C语言程序设计》读书笔记(第10章——指针)_第34张图片

代码解释:有一个函数(假设函数名为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参为两个函数名f1和f2,给形参传递的是函数f1和f2的地址。这样在函数sub中就可以调用f1和f2函数了。

实例:

#include 

/**
 * 计算两个数之间的最大值
 * @param a 第一个数
 * @param b 第二个数
 * @return 两数的最大值
 */
int max(int a, int b) {
    return a > b ? a : b;
}

/**
 * 计算两个数之间的最小值
 * @param a 第一个数
 * @param b 第二个数
 * @return 两数的最小值
 */
int min(int a, int b) {
    return a > b ? b : a;
}

/**
 * 计算两数之间最大值和最小值之间的平均值
 * 注意int (*MAX)(int, int)表示一个代表函数的形参,其中MAX是形参函数名,而(int, int)是参数列表,有几个参数就写几个,但是括号内是参数的类型
 * @param MAX 传入能够求两数之间最大数的函数
 * @param MIN 传入能够求两数之间最小数的函数
 * @param x 待计算的第一个数
 * @param y 待计算的第二个数
 * @return 返回最大值和最小值之间的平均值
 */
int avg(int (*MAX)(int, int), int (*MIN)(int, int), int x, int y) {
    // 调用传入的MAX函数,这里的MAX是形参,求两数的最大值
    int m1 = MAX(x, y);
    // 调用传入的MIN函数,这里的MIN是形参
    int m2 = MIN(x, y);
    return (m1 + m2) / 2;
}

int main() {
    // 将两个函数名max和min作为实参传入avg函数中
    int result = avg(max, min, 10, 20);
    // 打印结果
    printf("%d", result);
}

10.5.3 返回指针值的函数

一个函数可以返回一个整型值、字符值、浮点数值,也可以返回指针型的数据,即地址。这种返回指针值的函数,一般定义形式为:

// 语法
类型名 *函数名(参数列表);

// 示例
int *a(int x, int y);

实例:

#include 

/**
 * 计算两个数之间的最大值
 * @param a 第一个数
 * @param b 第二个数
 * @return 两数的最大值
 */
int* max(int* a, int* b) {
    int* m;
    if (*a > *b) {
        m = a;
    } else {
        m = b;
    }
    return m;// 返回一个指针变量
}


int main() {
    int a = 5, b = 10;
    int* x = &a;
    int* y = &b;

    int* result;
    result = max(x, y);// 来接收函数返回的指针变量
    printf("%d", *result);// 10
}

10.5.4 指针函数和函数指针的区别

  • 指针函数是带指针的函数,即本质是一个函数。
  • 函数指针是指向函数的指针变量,因而函数指针本身首先应是指针变量,指不上该指针变量指向函数。

10.5.5 函数参数传递的三种方式

10.5.5.1 概述

推荐博文链接:C语言中函数参数传递的三种方式_魏波-CSDN博客_c语言函数参数传递方式三种

C语言中函数参数传递的三种方式:

  • (1)传值,即传变量的值给函数作形参,在函数内对形参的改变不会影响到函数外该变量的值。
  • (2)传址,即传变量的地址赋给函数里形参的指针,使指针指向真实的变量的地址,在函数内对参数的修改会影响到函数外变量的值。
  • (3)传引用,实际是通过指针实现,效果如传址(即在函数内对参数的修改会影响到函数外变量的值),使用方式却是传值(即像传普通变量一样直接传入变量名即可)。实际上C语言中不能使用,是C++的。
#include 

void change(int* a, int &b, int c) {
    c = *a;
    b = 30;
    *a = 20;
}

int main() {
    int a = 10, b = 20, c = 30;
    change(&a, b, c);
    printf("%d %d %d", a, b, c);
}

/*
总结:
(1)指针传参(int *a),将变量的地址直接传入函数,可以修改它的值
(2)引用传参(int &b),将变量的引用传入函数,效果和指针相同,也可以对其值进行修改
(3)值传参(int c),对函数外部的变量c没有任何影响
*/

10.5.5.2 值传参

形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。

#include 

/********************************
值传参 
*********************************/

/**
 * 交换两个变量的值
 * @param x 
 * @param y 
 */
void swap(int x, int y) {
    int temp;
    temp = x;
    x = y;
    y = temp;
}

int main() {
    int a, b;
    printf("请输入待交换顺序的两个整数:");
    scanf("%d %d", &a, &b);
    swap(a, b);// 直接交换两个整数,不成功 
    printf("调用交换函数后的结果是:%d %d\n", a, b);
    return 0;
} 

10.5.5.3 指针传参

形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

#include 

/********************************
指针传参 
*********************************/

void swap(int *x,int *y)
{
	int temp;
	temp=*x;
	*x=*y;
	*y=temp;
}

int main()
{
	int a,b;
	printf("请输入待交换顺序的两个整数:");
	scanf("%d %d",&a,&b);
	swap(&a,&b);// 交换两个整数的地址,成功 
	printf("调用交换函数后的结果是:%d %d\n",a,b);
	return 0;	
} 

10.5.5.4 引用传参

这是C++中才有的。形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。

#include 

/********************************
引用传参 
*********************************/

void swap(int &x,int &y)// 注意,形参的参数名必须在名字之前加上&符号
{
	int temp;
	temp=x;
	x=y;
	y=temp;
}

int main()
{
	int a,b;
	printf("请输入待交换顺序的两个整数:");
	scanf("%d %d",&a,&b);
    // 传实参的时候,只需要同值传参一样,传入普通的变量名即可
	swap(a,b);// 交换两个整数的地址,成功 
	printf("调用交换函数后的结果是:%d %d\n",a,b);
	return 0;	
} 

10.6 指针数组和指向指针的指针

10.6.1 指针数组的概念

指针数组的概念:一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式为:

// 语法
类型名* 数组名[数组长度];

// 示例
int* nums[4];

实例:

#include 

void main()
{
	int a[5]={1, 3, 5, 7, 9};
	int *name[5]={&a[0], &a[1], &a[2], &a[3], &a[4]};
	int i;
	
	for(i=0;i<5;i++){
		printf("%d ", *name[i]);
	}
	printf("\n");
}

img

应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。

二维数组指针变量是单个的变量,其一般形式中"(*指针变量名)“两边的括号不可少。而指针数组类型表示的是多个指针(一组有序指针)在一般形式中”*指针数组名"两边不能有括号。例如:

  • int (*p)[3];:表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为3。
  • int *p[3];:表示p是一个指针数组,有三个下标变量p[0],p[1],p[2]均为指针变量。

10.6.2 指向指针的指针

定义一个指向指针数据的指针变量的形式:

char **p;

p的前面有两个*号。*运算符的结合性是从右到左,因此**p相当于*(*p),显然*p是指针变量的定义形式。如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。

《C语言程序设计》读书笔记(第10章——指针)_第35张图片

现在前面又有一个*号,表示指针变量p是指向一个字符指针变量的。*p就是p所指向的另一个指针变量。

#include 

void main(){
	char *name[]={"C", "Java", "HTML", "CSS", "JavaScript"};
	char **p;
	int i;
	
	for(i=0;i<5;i++){
		p=name+i;
		printf("%s\n", *p);
	}
}

《C语言程序设计》读书笔记(第10章——指针)_第36张图片

10.7 总结

10.7.1 有关指针的数据类型的小结

定义 含义
int i; 定义整型变量i
int* p; p为指向整型数据的指针变量
int a[n]; 定义整型数组a,它有n个元素
int* p[n]; 定义指针数组p,它由n个指向整型数据的指针元素组成
int (*p)[n]; p为指向含有n个元素的一维数组的指针变量
int f(); f为带有整型返回值的函数
int* p(); p为带有指针返回值的函数,该指针指向整型数据
int (*p)(); p为指向函数的指针,该函数返回一个整型值
int **p; p是一个指针变量,它指向一个指向整型数据的指针变量

10.7.2 字符指针变量指向的字符串常量中的内容是不能修改(不能对它再赋值)

在写一个交换数组元素顺序的程序时,遇到了这个问题:

#include 

void print(char *str)
{
	while(*str!='\0')
	{
		printf("%c",*str);
		str++;
	}
}

int length(char *str)
{
	int count=0;
	while(*str!='\0')
	{
		count++;
		str++; 
	}
	return count;
}

void swap(char &a, char &b)
{
	char temp;
	temp=a;
	a=b;
	b=temp;
}

void reverse(char *str)
{
	int i=0, j=length(str)-1;
	while(i<j)
	{ 
		swap(str[i], str[j]);
		i++;
		j--;
	}
}

int main()
{
	char *str="Hello World!";
	reverse(str);
	print(str);
	
	return 0;
}

img

可以看到没有任何的结果输出。但对char *str="Hello World!"修改为char str[]="Hello World!"后,就可以了。

img

原因是在reverse函数内通过swap()方法操作了字符指针所指向字符串的数据。

在一个双引号""内的字符序列或者转义字符序列称为字符串常量。例如:“Hello World!”。

这些字符串常量是不能改变的,因为这些字符串常量是存在静态内存区的,不可以改变。

例如下面的代码就是非法的:

#include 

int main()
{
	char *a="Hello World!";
	printf("%s\n",a);
	
	a[2]='N';// 非法的,但也不会报错
	printf("%s\n",a);// Hello World!
	
	return 0;
}

因为a[2]本来就是一个常量,不能被改变,但可以获取它的值。

但如果把*a改成a[]就不会有问题,例如:

#include 

int main()
{
	char a[]="Hello World!";
	printf("%s\n",a);// Hello World!
	
	a[2]='N';
	printf("%s\n",a);// HeNlo World!
	
	return 0;
}

这个a[2]就是一个char类型的变量,可以改变值。但这样又是可以的:

#include 

int main()
{
	char *a="Hello World!";
	printf("%s\n",a);
	
	a="Hello C!";
	printf("%s\n",a);
	
	return 0;
}

10.7.3 字符指针变量作为形参在函数内是否修改值

在函数形参中,字符指针变量作为形参,一旦在函数内改变其值,那么在函数外的实参值也会发生改变。但如果仅仅是指针向后或向前移动一个或几个位置,那么函数外部的变量没有任何影响。实例如下:

#include 

void withChangePos(char *str)
{
	printf("函数内前:%d\n",str);
	while(*str!='\0')
	{
		printf("%c", *str);
		str++;// 指向数组的下一个元素 
	}
	printf("\n函数内后:%d\n",str);
}

void withChangeValue(char *str)
{
	str[0]='0';
	str[1]='1';
	str[2]='2';
}

int main()
{
	char str[]="Hello World!";
	withChangePos(str);
	printf("函数外:%d\n\n",str);
	
	withChangeValue(str);
	printf("%s\n",str);

	return 0;	
} 

《C语言程序设计》读书笔记(第10章——指针)_第37张图片

10.7.4 关于&*在函数内的相互使用

#include 

void printB(char *b)// *b参数 
{
	printf("%c\n",*b);
}

void printA(char &a)// &a参数 
{
	printf("%c\n",a);
	printB(&a);// 调用指针变量的函数,这边传的实参是&a 
} 

int main()
{
	char b='A';
	printA(b);

	return 0;	
} 

你可能感兴趣的:(读书笔记,c语言,单片机,开发语言)