c语言数组名和指针的区别 (深入理解c语言书籍)

1. 定义:指针是一个值为内存地址的变量;

地址

数据

0x1000

0x1004

int a=5;

0x1008

5

0x100c

0x1010

0x1014

int *pint1=&a;

0x1018

0x1008

0x101c

int **pint2=&pint1;

0x1020

0x1018

0x1024

2. 声明:声明指针变量时必须指定指针所指向变量的类型;

指针声明示例

#include <stdio.h>

#include <stdlib.h>

int main( void ) {

char * pchar; //char * pchar//char *pchar

int * pint;

double * pdouble;

printf( "%d %d%d\n" , sizeof ( char ), sizeof ( int ), sizeof ( double ));

//输出字节数分别为 1 4 and 8

printf( "%d %d%d\n" , sizeof (pchar), sizeof (pint), sizeof (pdouble));

//输出指针变量字节数 4 4 and 4; win32为4个字节; win64为8个字节

system( "pause" );

return 0;

}

Eg: char * pchar;声明pchar是一个指针变量,其类型为char *,pchar是指向char类型变量的指针,即*pchar是char类型;注意指针变量的字节数与计算机位数相对应,32位为4个字节,64位为8个字节;

3. 指针初始化

#include <stdio.h>

#include <stdlib.h>

int main( void ) {

char charc = 'a' ;

char * pchar;

pchar =&charc;

int inti = 1;

int * pint;

pint =&inti;

double doubled = 1;

double * pdouble;

pdouble =&doubled;

printf( "%p %p%p\n" , pchar, pint, pdouble);

//输出十六进制地址分别为:00EFF9B3 00EFF998 00EFF97C

//输出十进制地址分别为: 15727027 15727000 15726972

printf( "%d %d%d\n" , sizeof (pchar+1), sizeof (pint+1), sizeof (pdouble+1));

//输出字节数分别为4 4 and 4

printf( "%p %p%p\n" , pchar + 1, pint + 1, pdouble + 1);

//输出十六进制地址分别为:00EFF9B4 00EFF99C 00EFF984

//输出十进制地址分别为: 15727028 15727004 15726980

system( "pause" );

return 0;

}

Eg: char * pchar; pchar = &charc;等价为 char * pchar =&charc; pchar + 1等价为 &charc + 1, pchar值为 00EFF9B3, pchar+ 1等价为 00EFF9B3 + 1×sizeof ( char )等价为 00EFF9B4 ;同理 pint+ 1 等价为 &inti + 1, pint值为 00EFF998, pint+ 1等价为 00EFF998 + 1×sizeof (int)等价为 00EFF99C ; pdouble + 1 等价为 &doubled + 1, pdouble值为 00EFF97C, pdouble+ 1等价为 00EFF97C + 1×sizeof ( double )等价为 00EFF984 ;

注意

pchar + 1 ≠ &charc + sizeof ( char );同理 pint+ 1 ≠ &inti + sizeof ( int ); pdouble+1 ≠ &doubled+ sizeof ( double );

4、指针的类型与其所指向的对象类型需严格匹配

#include <stdio.h>

#include <stdlib.h>

int main( void ) {

double doubled = 1;

double * pdouble =&doubled; //正确: 初始值是double型对象的地址

double * pdouble2 =pdouble; //正确:初始值是指向double型对象地址的指针

int * pint = pdouble; //错误:指针pint的类型和指针pdouble的类型不匹配

pint =&doubled; //错误:试图把double型对象的地址赋给指向int型对象地址的指针

system( "pause" );

return 0;

}

Eg:“初始化”: 从“double *”到“int *”的类型不兼容

5、数组名

一维数组作为函数形参时写法如下:

类型名 数组名[]

注意: 不用写出数组的元素个数。

[1] 一般情况下,一维数组名等价为常量指针;

一维数组名并表示整个数组,一维数组名的值实际上是一个常量指针,也就是数组第1个元素的地址,其类型取决于数组元素的类型: 如果它们是 int 类型,那么一维数组名的类型就是“指向int类型的常量指针”;如果它们是其他类型,那么一维数组名的类型就是“指向其他类型的常量指针”。

注意:常量指针不能被赋值;

Eg: int arr[5] = {1,2,3,4,5 };

int * pint;

arr=pint; /错误赋值

[2] 特殊情况下,一维数组名不可以等价为常量指针;

(a)当一维数组名作为sizeof操作符的操作数时,它返回该数组的长度,以字节为单位,而不是指向数组的指针的长度。

Eg:sizeof (arr)为20个字节

(b)取一个一维数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

Eg: &arr[0],arr,pint和 &arr的值为 004FF8C4

&arr[0]+1等价为 004FF8C4+ 1×sizeof (int)等价为 004FF8C8 ;

arr+1等价为 004FF8C4+ 1×sizeof (int)等价为 004FF8C8 ;

pint+1等价为 004FF8C4+ 1×sizeof (int)等价为 004FF8C8 ;

&arr+1等价为 004FF8C4+ sizeof (arr)等价为 004FF8C4+sizeof (int)等价为 004FF8D8 ;

示例程序如下:

#include <stdio.h>

#include <stdlib.h>

int main( void ) {

int i = 0;

int arr[5] = { 1,2,3,4,5 };

int arr2[5];

int * pint;

int * pint2;

int * pint3;

pint = &arr[0]; //等价为pint=arrr;

//pint2被赋值的是一个常量指针的拷贝,pint2 所指向的是数组的第1个元素的地址。

for (i = 0; i < 5; i++) {

arr2[i] = arr[i];

printf( "%d " , *(arr2 + i)); //输出值分别为: 1 2 3 4 5

}

//数组名并不表示整个数组,不能使用arr2= arr来表示整个数组被复制到一个新的数组;

printf( "\n %d %d %d %d\n" , sizeof (&arr[0]), sizeof (arr), sizeof (pint), sizeof (&arr));

//输出值分别为:4 20 4 4

printf( " %p %p %p %p\n" , &arr[0],arr,pint,&arr);

//输出值分别为: 004FF8C4 004FF8C4 004FF8C4 004FF8C4

//输出十六进制值分别为: 5241028 5241028 5241028 5241028

//&arr:类型为int[5] *; &arr是一个指向包含5个整型元素的数组的指针。

//&arr[0]:类型为int *; &arr[0]是数组第一个整型元素的地址。

//pint:类型为int *; pint是一个指向数组第一个整型元素的指针。

//arr:类型为int[5]; arr是一个常量指针,其值是pint

printf( " %d %d %d %d\n" , sizeof (&arr[0] + 1), sizeof (arr + 1), sizeof (pint + 1), sizeof (&arr + 1));

//输出值分别为: 4 4 4 4

printf( " %p %p %p %p\n" , &arr[0]+1, arr+1, pint+1, &arr+1);

//输出值分别为: 004FF8C8004FF8C8 004FF8C8 004FF8D8

//输出十进制值分别为: 5241032 5241032 5241032 5241048

system( "pause" );

return 0;

}

6、使用指针变量作为形参交换两个整数的值

补充知识

函数的定义

返回值类型 函数名(参数1类型 参数1名称, 参数2类型参数2名称……)

{

语句组(即“函数体”)

}

如果函数不需要返回值,则“返回值类型”可以写“void”。

在函数返回值类型为“void”时,使用指针变量作为形参交换两个整数的值。

一维数组作为函数参数时,是传引用的,即形参数组改变了,实参数组也会改变。

(1)错误用法

#include <stdio.h>

#include <stdlib.h>

//函数的形参是实参的一个拷贝,且形参的改变不会影响到实参

//(除非形参类型是数组、引用)

void swap( int a , int b ) {

//注意主函数中&a与&b地址与子函数中&a与&b地址不一样;

printf( "swap:%p, %p\n" , & a , & b ); //swap:00BAFA7C,00BAFA80

int tmp;

tmp = a ;

a = b ;

b = tmp;

printf( "swap:%d, %d, %p, %p\n" , a , b , & a , & b );

//swap:3, 1, 00BAFA7C, 00BAFA80

}

int main( void ) {

int a, b;

a = 1;

b = 3;

swap(a, b);

printf( "%p, %p\n" , &a, &b); //00BAFB60, 00BAFB54

printf( "%d, %d\n" , a, b); //1, 3

system( "pause" );

return 0;

}

(2)错误用法

#include <stdio.h>

#include <stdlib.h>

void swap( int tc [], int td []);

int main( void ) {

int c, d;

c = 5;

d = 7;

int * pc = &c;

int * pd = &d;

swap(pc, pd);

printf( "%d, %d,%p,%p\n" , c, d, pc, pd);

//输出值分别为: 5, 7,00D8FCCC,00D8FCC0

system( "pause" );

return 0;

}

void swap( int tc [], int td []) {

int * ptmp;

ptmp = tc ;

tc = td ;

td = ptmp;

printf( "swap:%d, %d,%p,%p\n" , * tc , * td , tc , td );

//swap:7,5,00D8FCC0,00D8FCCC

printf( "swap:%d, %d\n" , sizeof ( tc ), sizeof ( td ));

//swap:4, 4

}

(3)正确用法

#include <stdio.h>

#include <stdlib.h>

//函数的调用语句前面有函数的声明即可,不一定要有定义!

void swap( int * tc , int * td );

//等价声明法2 //参数名称可以省略,它不属于函数的原型,函数声明也称为“函数的原型

//void swap(int*,int*);

//等价声明法3

//void swap(inttc[], int td[]);

int main( void ) {

int c, d;

c = 5;

d = 7;

int * pc = &c;

int * pd = &d;

swap(pc, pd);

printf( "%d, %d,%p,%p\n" , c, d, pc, pd);

//输出值分别为 7, 5,010FFC30,010FFC24

system( "pause" );

return 0;

}

void swap( int * tc , int * td ) {

int tmp;

tmp = * tc ;

* tc = * td ;

* td = tmp;

printf( "swap:%d, %d,%p,%p\n" , * tc , * td , tc , td );

//输出值分别为 swap:7, 5,010FFC30,010FFC24

}

7. 使用指针访问数组

*(parr + i)等价为 parr[i]等价为 arr[i]等价为 *parr++,注意使用*parr++访问数组时,parr所指向变量的地址发生变化,要特别提醒非法越界访问地址,见示例程序2

示例程序1

#include <stdio.h>

#include <stdlib.h>

int main( void ) {

int arr[5] = { 1,3,5,7,9 };

int i = 0;

//通过指针访问数组

int * parr;

//数组名代表的是数组第一个元素在内存中的地址,等价于常量指针

parr = &arr[0];

//等价于&arr[0], arr, &arr, parr,注意此处等价指的是值相等

//法1

for (i = 0; i < 5; i++) {

printf( "%d " , *(parr + i)); //1 3 5 7 9

}

printf( "\n " );

//法2

for (i = 0; i < 5; i++) {

printf( "%d " , parr[i]); //1 3 5 7 9

}

printf( "\n " );

//法3

for (i = 0; i < 5; i++) {

printf( "%d " , arr[i]); //1 3 5 7 9

}

printf( "\n " );

for (i = 0; i < 5; i++) {

//*,+两个操作符,*的优先级更高

printf( "%d " , *parr + i); //1 2 3 4 5

}

printf( "\n %d, %d\n" , sizeof (arr), sizeof (parr)); //20 4

printf( "%p %p %p %p\n" , &arr[0], arr, &arr, parr);

//0056FAA0 0056FAA0 0056FAA0 0056FAA0

system( "pause" );

return 0;

}

示例程序2

#include <stdio.h>

#include <stdlib.h>

int main( void ) {

int arr[5] = { 1,3,5,7,9 };

int i = 0;

//通过指针访问数组

int * parr;

//数组名代表的是数组第一个元素在内存中的地址,等价于常量指针

parr = &arr[0];

printf( "%p %p %p %p\n" , &arr[0], arr, &arr, parr);

//输出值分别为:00BAF8EC 00BAF8EC 00BAF8EC 00BAF8EC

for (i = 0; i < 5; i++) {

printf( "%d " , *parr++);

//1 3 5 7 9 *parr++等价为*(parr++)

}

//前缀自增运算符,返回的是自增后的值

//后缀自增运算符,返回的是自增前的值。即*parr

//使用parr++ 时要注意避免访问非法地址

printf( "\n %d" , *parr); // -858993460

printf( "\n %d, %d\n" , sizeof (arr), sizeof (parr)); //20 4

printf( "%p %p %p %p\n" , &arr[0], arr, &arr, parr);

//输出值分别为:00BAF8EC 00BAF8EC 00BAF8EC 00BAF900

for (i = 0; i < 5; i++) {

printf( "%p " , &arr[i]);

//00BAF8EC 00BAF8F0 00BAF8F4 00BAF8F8 00BAF8FC

}

system( "pause" );

return 0;

}

参考文献

C和指针 徐波译

C Primer Plus第6版 姜佑译

C++ Primer 第5版 王刚等译