c语言学习--指针 与 其他类型(数组、字符串)


本文重在讨论指针,对数组和函数只是提及对比

指针简介

指针: 就是一个值为内存地址的变量
就如 char 的值是字符,int的值是整数,指针的值是地址

简单使用实例

int a = 9;  //定义一个整型变量,并初始化为 9
int *p;   //定义一个指向整型的指针变量
p = &a; ,  //将a的地址存在指针变量p中
*p ;      //取指针变量p存的地址内的值,就是a的值

间接运算符 * (解引用运算符)

作用是取出一个指针变量存的那个地址里面的值,简单来说就是取一个地址的存放的值

在声明指针时也用于表示这是一个指针变量

地址运算符 &

跟在一个变量名后,会取出这个变量的地址
如 &a,代表a的地址

声明指针

声明指针变量时必须指定指针所指向的变量的类型

注意指针是一种新的类型,不用把它当做整型处理

虽然不同类型的指针占的空间大小是一样的(地址长度),但是它所指向的类型的大小不一样,声明指针时告知类型,就可以根据实际类型做出处理
例如p++,如果p指向的是int,那么p存储的地址将+4,(一个int 4个字节)

int *p;
char *pc,*pc2;
.....

指针操作

int n[5];
int * p1, *p2, *p3;
p1 = n;  //赋值
p2 = &n[2] //赋值
p3 = p1 + 4;  // 加分
printf("%d\n",*p3);   // 解引用
p2++;    //自增
p2--;   //自减
p3 - p1;  // 指针之间的运算

赋值 :可以使用数组名、&变量名,另一个指针赋值
解引用:*取出指针存储的地址内 的值
取地址:&取出一个变量或对象的首地址
指针和整数进行加减:地址加或减一个数据类型的大小
指针求差: 通常用于求一个数组不同元素的距离,得到的值是相差的元素,而不是字节数,只能对同一个数组的元素使用

注意,不要解引用未初始化的指针,可能会操作一个未知空间,或者报错

函数:指针形参

函数如果希望直接改变实参的值,就需要使用指针作为形参,将实参的地址传给函数,该函数直接操作其地址空间,就能直接改变实参的值

实例

void change( int *a, int *b)
{
	int c ;
	c = *a;
	*a = *b;
	*b = c;
}
//调用时
change( &a, &b);

注意change需要的参数是一个int类型的地址
所以调用时需要 &a

也因为传进函数的是一个地址
所以在函数内使用需要 *a

数组简介

数组由一系列数据类型相同的元素组成

声明和初始化

声明一个数组需要告知数据类型和个数,在声明的同时可以初始化

实例

char c[10];
int a[5] = { 1,2,3,4};
int b[] = {4,5,6,4,9};
int d[5] = {[1] = 1,2,3,4};

如a,在初始化列表的个数少于数组元素个数时,剩余的元素会被初始化为
如b,不实际指出数组元素的个数,编译器会设置为足够放下初始化的值
如c,可以在初始化列表内使用带方括号的下标值,指定代初始化的元素

注意c语言不允许,将一个数组直接整体赋值给另一个数组,除了初始化外,也不允许使用花括号的形式赋值

元素个数与数组边界

在定义时,元素个数必须是正整数(不包括0),在c99之前也不允许使用变量代表元素个数
如下

int a1[0];   //不合法
int a2[-1];   //不合法
int a3[2.3];   //不合法
int a1[(int)2.3];   //合法,强制转换为整型

c99也引入了可变长数组,但是这里不讨论

数组下标是从0开始的,意味着,20个元素的数组,访问范围是0~19,如果超过此边界,可能会改变其他变量的值,或者导致异常退出

多维数组

如定义及初始化实例

int a[2][2] = { 1,2,3,4};  //内有两个数组,每个数组都是包含两个int的

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

如上 a 的首元素 a[0]代表一个含两个int的数组,a[0]的首元素是a[0][0]其是一个int类型的变量

一个变量看做一个数据,那么一维数组就是一排数据,二维数组就是一个表格,三维数组就是一叠表格

二维数组可以看做一个二维的表格,但是实际上计算机存储时是线性存储的,就是一排存过去,先存a[0]再存a[1]

指针、数组、函数

数组名就是其首元素的地址,但是数组名是一个常量,不能作为指针使用,其地址不可改变

flizny == &flizny[0]   //成立

如果希望能够自增或自减来获取数组元素,可以将数组名的地址赋值给一个指针,再改变指针变量的值

地址是内存中的每个字节都按顺序编号,如果是一个较大对象的地址(数组,结构体),一般使用其首元素来表示其地址

指针+1,并不是代表地址上的+1,而是指向了本类型的下一个存储单元,对数组而言就是指向了下一个元素(地址增加一个类型的大小)

另外c语言保证,指向超过数组范围的后面第一个元素依然是有效的指针,但是不要访问前面一个,和后面两个等

int a[5] = {0};
int *p = a;  //a本身就是数组的地址,所以不需要&,其他变量取地址需要使用&
p++;   //假设 a的地址是1000,那p++之后地址就变成了1004  (int为4字节) 
a+5   //有效
a++  //违法操作,a是常量不可以自增

指针表示法和数组表示法

指针和数组的关系十分密切,两种表示法都可以获得正确的结果,实际上是对一个对象的两种表示方法

//指针表示法 \=\= 数组表示法
    data + 2 == &data[2] 
   (data +2) ==  data[2]
//但是需要注意
*(data +2) 表示的是data[2]
(*data + 2) 表示的是data[0] + 2

以上应该不难理解,(*data + 2)是先对data取值操作,再加2

数组形参

由于数组名就是一个地址,所以数组形参实际上和指针形参类似,如下实际上效果差不多,对编译器而言是差不多的

int add(int *data);
int add(int data[]);

在定义数组的函数中,我们可以使用sizeof求得数组的大小,但是当数组作为形参传递给另一个函数,实际上传递过去的只是首地址,这时候已经无法使用sizeof求得大小,使用我们一般还会使用一个参数传递数组大小

int add(int data[], int n);

再如

int main(void)
{
	int a[10] = {0};
	add( a, 10);
}
int add(int data[], int n)
{
	data[1] = 1;
}

在以上代码中,如果sizeof(a),将为40,a代表一个有10个int元素的数组
但是 sizeof(data)在64位系统中大小为 8,就是指针的大小,data只是一个指向a的指针,而不是数组

const 和 指针

之前定义的使用数组形参的函数,都是传地址的,函数中可以改变实参的值,如果不希望改变 可以形参声明为const 如下

int add(const int a[], int n);

这样子并不是要求原数组是常量数组,而是在函数处理时将其视为常量

此外,函数声明const形参可以接收普通数组作为实参
但是,函数的非const形参不能接收const实参(这样子的行为是未定义的)

常量指针

int a[5] = {0};
int b[9] = {0};
const int *p = a;
p = b;
*p = 4; //违法操作

常量指针,就是常量的指针,(const int)类型的 *p,p内存储的地址被当做常量处理

以上定义意味着,不能通过p来修改a的值

虽然不能通过p修改a,但是p本身的值(a的地址)是可以改变的,也就是说p可以指向其他元素

另外,const变量和非const变量 都可以赋值地址给 const指针
但是,const变量 不能赋值地址给 非const指针,如果可以的话就可以通过指针改变const变量的值了(这样子的行为是未定义的)

指针常量

int a[5] = {0};
int b[9] = {0};
int * const p = a;
*p = 3;
p = b   //违法操作

指针常量,就是 一个指针类型的常量
可以通过指针常量去修改地址上的值
但是不能改变这个指针存储的地址(一个这个指针是个常量,而它指向的地址不是)

const int * const a

int a[5] = {0};
int b[9] = {0};
const int * const p = a;
p = b   //违法操作
*p = 4; //违法操作

这个指针const即修饰int 也修饰指针
所以不能通过它改变地址的值,也不能改变它的值

指针和多维数组

二维数组和两重解引用

int a[4][3];

如上定义的二维数组中

  • a是数组的首元素地址,其值和 &a[0] 相等,而a[0]又是一个内含3个元素的数组的首地址 其值和 &a[0][0] 相等,所以a = &a[0] = &a[0][0]
  • 上述的值相同是因为都是同一个地址,而他们代表的含义却不一样,a内存储的地址是一个内含3个int的数组的地址,而a[0] 内存储的是一个int的地址
  • 所以a++,地址增加12,而a[0] ++ 地址增加 4
  • *a :因为a内存储是数组a[0]的地址,所以*a 的值为 &a[0][0]
  • *( a[0] ): a[0] 内存储的是一个int的地址( a[0][0]地址 ),所以*(a[0]) 的值为a[0][0]
  • *a 相当于( a[0] ) 相当于 *( &a[0][0] ), *与&抵消,可知其值为a[0][0]

数组的指针

定义一个内含两个int的的数组的指针

int (* p)[2]

指针数组

定义一个int的类型的指针数组,内有两个指针

int * p[2];

如上,是因为[ ]的优先级比*高,先与p结合成两个元素的数组,然后 * 代表p
数组内是两个指针

二维指针形参

正确定义:

int add( int a[][3],int n);

一般只允许,最左边的括号是空的,右边的括号都需要有确定的值
这是因为,最左边的空括号告知编译器,这是一个指针,而后面括号是为了告诉编译器参数的类型,如上参数类型是指向内含3个int的数组指针

假如在函数内操作 a+ 1,那么编译器就知道应该给地址+12个字节,如果后面的括号也空,那编译器就无法确定了

还有另外一种定义方式

typedef int arr4[4];
typedef arr4 arr3X4[3];
int add(arr3X4 ar, int n);
等价于 :int add(int arr[3][4], int n);
等价于 :int add(int arr[][4], int n);

编译器会忽略最左边的括号的值,只表明了这是一个指针

指针的兼容性

指针之间的赋值,比其他类型的要严格
如下所示

int *pt;
double *pd;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2;

主要看,这个地址的对象是代表什么类型,比如ar1[0],代表ar1[0][3]这个数组的首地址,这个地址的内容是一个int类型

  • pt = &ar1[0][0],合法,地址都是指向int
  • pt = ar1[0] ,合法,地址都是指向int
  • pt = ar1 ,无效,ar1是数组 { ar1[0] , ar1[1] } 的首地址,内容是ar[0],代表3个int的数组
  • pa = ar1,合法,都是指向3个int的数组
  • pa = ar2,无效,ar2是指向2个int的数组
  • *p2 = ar1[0],合法都是指向int
  • p2 = ar2,无效,p2指向一个指针,这个指针指向int,ar2是指向数组的指针

字符串与指针

字符串就是一个以空字符(\0)为结尾的char类型数组,因此数组和指针的知识也可以在这里用上

字符串字面量 (常量)

用双引号括起来的内容就是字符串字面量 (常量),如下

“sdfsafsad”
“nnnn”

字符串字面量 (常量)是静态存储类别,只会存储一次,在整个程序生命期都存在
在函数中用双引号括起来的内容被视为 指向这个字符串存储位置的指针

如下,a中存储了字符串的首地址,但是a只是一个char指针,而不是字符串,该字符串作为可执行文件的一部分存储在数据段中,也不允许修改。

char *a = "aldjfak";
printf(" %s \n", a);

字符串数组

定义字符串数组时需要告诉编译需要多少空间,如果需要输入字符串,只能用这种形式

a[40] = "a;df;af;a;df";

数组空间是40,而初始化列表内没有40个,剩下的空间会被初始化为\0
注意数组的空间至少要比字符串多1,用于容纳 最后那个\0
编译器会自动在字符串后添加一个\0

指针形式 和 数组形式 区别

数组形式 在内存中分配了40个,其中每个空间对应一个字符(最后有一个\0)
初始化时,每个元素被初始化为 字符串常量的字符

字符串常量是在可执行文件(数据段)中存储的,而数组是运行后才分配空间并初始化,其内容是从字符串复制过来的

  • 指针形式 只是存储了字符串常量的首地址,没有复制字符串,也无法修改值,指针是变量,可以修改地址的值
  • 数组形式 复制了字符串常量的内容,可以修改数组中的内容,但是字符串常量也不被修改,数组名是常量,不能修改
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GuanFuXinCSDN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值