C语言的指针与结构体

(一)指针   

  了解指针首先要理解地址,我们是通过”&”运算符来获得一个变量的地址地址可以通过%p进行输出,尽量不要用%x的十六进制输出,以后就这样来看变量的地址。

 

  取地址符的操作数只能是变量,不可以有&(i++)这样的写法。

 

  尝试了对于普通变量进行取地址,我们尝试对数组来取地址:

 

int a[]={1,2,3};
printf("%p\n",a);
printf("%p\n",&a);
printf("%p\n",&a[0]);
printf("%p\n",&a[1]);

控制台输出:

000000000062FE40
000000000062FE40
000000000062FE40
000000000062FE44

  可见对于数组的取地址运算,可以不带取地址符,并且对于数组取的地址就是数组第一个元素所在的地址。

 

 

  我们知道了变量是有地址的,而且通过取地址符我们可以获得变量的地址,那么我们遇到的问题就是两个:

    1.取到的数组应该放到什么变量里 2.如何直接访问地址里的内容

 

  第一个问题:我们是通过指针这种变量来保存地址的。

  定义一个指针来存储地址的语法是:

    Int*p=&i; 

也就是定义了一个指针p来存储i的地址,我们也叫做指针p指向i,如果希望同时定义两个指向int型变量指针变量,请用:    

    int*p,*q;  

  C语言当中没有int*这种数据类型,诸如int*p,q,其实是定义一个指向int变量的指针变量pint型变量q

 

  当指针做参数时,做形参基本的格式是 void f(int *p); 作实参时:f(&i) ,这样我们就在函数里得到了外部i的地址,存在指针p里,这样函数就可以访问外部的i了于是我们有*运算符: *p表示访问指针p所指向的对象,此时*p可以作左值,也可以作右值,也就是说我们既可以读也可以改这个参数:

 

void fun(int *p){
*p=66;
}
int main(){
int t=22;
int *p=&t;
printf("%d\n",t);  
fun(p);
printf("%d\n",t);
return 0;
}

控制台输出:

22
66

  虽然此时仍然是遵循函数值传递的基本规则,但是借助传指针我们实现了函数访问函数外的变量。

 

  我们知道了传普通变量和传指针都是值传递,那么传数组是什么情况呢?函数形参的数组变量接收到了什么呢?我们来看看int []a不作形参和作形参时sizeof(a)的具体表现说起:

  对于形参的数组名取sizeof()我们会得到当前系统字长数/8,而在函数外则能得到数组的长度,原因在于实际数组传入函数内时,函数名已经变成一个普通指针了,换句话说sizeof((a[]),q其实就是sizeof(int *p),所以得到的是指针所占的字节数。

    那么数组变量是什么呢?数组变量是一种指针常量:int* const p,所以不能通过a=b的方式用一个数组对于另一个数组进行赋值,而可以定义一个指针变量,用数组名去初始化,指针所支持的*可以用于数组,数组支持的[]可以用于指针。

 

  那什么是指针常量呢?一个地址涉及到了两个变量,也就是自己所在的指针变量,以及 自己指向的那个变量,这两个变量都可以被限制成常量,若是指针被限制不能修改,那么指针就不能指向其他地方,p++这种事情绝不能发生,我们称之为指针常量,若是指针指向的内容不能修改,我们称之为常量指针。

写法有:

int* const p;

const int*p;

int const*p;

三种,区分在于const后面跟着什么,什么就被限制了,看看它是什么变量,若是指针p,那就是指针变量p被限定了,所以就变成了指针常量,不是p而是*p那就是常量指针。

  常量指针是在形参当中出现的,表示传入的内容不可以被修改,比如:void f(const int* p);

调用时:

int k=7; 

int* q=&k;

f(q);

  q本身是一个指针变量,形式结合的时候初始化p这个常量指针,常量指针限定了指针所指向的内容不可以变化,所以*p不可以变化,也就是k不会发生变化。

  当然对于本例子,其实直接f(k),就能达到效果了,但是主要是用在我们需要将大体积的对象传入函数的时候,形实结合就要复制该对象,会比较耗费资源,若传对象的指针就很方便,但是有时我们只想用对象的值而不想它被改变,利用常量指针就可以解决这个问题,如果就是希望被改变那是不需要加const的。

  我们知道数组本身已经是指针常量了,但是我们也可以在数组名前加const,此时 const的意思是数组当中每一个元素都是const,相当于一堆变量都是const,我们知道常量是要定义时就初始化的,所以常数组在定义时也也马上初始化。若将这样的数组作形参,也就是数组的每一个元素都不能被修改,这样就能保护数组了。

 

指针类型作为一种变量类型,也具有与变量类型相关的各种运算:

(1)算术运算:有+,-,++,---,表示指针前进或者后退若干个元素,具体地址变化与指针所指向的数据的类型有关,减号表示相距几个元素,*p++可以实现对于特定尾巴数组的遍历:

int a[]={1,2,3,4,5,6,-1};
int *p=a;
while(*p!=-1){
printf("%d ",*p++);;
}

(2)赋值运算:每一个进程都有自己的零地址,用来完成一些特殊的任务,零地址不能写入内容,C语言当中预定义了一个零地址符号NULL,为了防止刚定义的指针里面残存的数据直接使用导致指针乱指,我们应该将它指向零地址,保证其安全无害,不建议使用字面值0;

(3)关系运算:由于内存地址是线性的,因此关系运算对于指针仍然适用。

(4)类型转换 可以使用(void *)将其转换为void型的指针,再转成其他类型的指针 


指针的用途:

    1.传入较大的数据时作参数

    2. 传入数组后,对数组进行操作

    3. 函数返回多个结果,指针带出结果

    4. 动态内存分配


动态内存分配:

为了在程序运行过程时确定数组的大小,需要动态分配内存,是通过malloc函数实现的:

malloc函数:

需要:<stdlib.h>头文件

函数原型: void* malloc(size_t count)   //参数是一个整数,返回一个void*类型的地址,根据需求转换 

一般的用法: int*p=(int*)malloc(num*sizeof(int)); 配合free(p);

注意事项:free的时候不能p++以后free,free的位置就不对,不能free非动态内存分配而来的内存,不能free以后再free

 

 

(二)结构体 

结构体:有些数据实际上需要多个类型的数据成员组成,于是引入了结构体,使用结构体本质是我们先声明一个类型,再定义该类型的变量,再对该变量进行赋值,作为变量也可以传参等等。

 

定义一个结构体类型:

第一个问题:我们应该把结构体声明在哪里?

我们推荐把它单独拿到所有函数外面,放在所有函数之前,这样所有函数都能使用该结构体类型

第二个问题:如何声明一个结构体:

(1) 先声明一个结构体,再定义结构体变量:

(2) 声明一个无名结构体,并直接定义所有我们需要全部的结构体变量

(3) 声明一个结构体并直接定义结构体变量:

 

 

//方法一:
struct date{
int month;
int day;
int year;
};

......

struct date today;//这一句在函数里,struct不可省略

//方法二:
struct date{
int month;
int day;
int year;
}today;   //声明了结构体马上拿到变量

//方法三:
struct {
int month;
int day;
int year;
}today,yesterday;   //一共就需要两个,全部拿了,结构体类型以后也不用了

 

第三个问题:如何初始化一个结构体变量?

  作为一个变量,你不初始化它,它里面就是垃圾数据,结构体的本质与数组非常接近,数组是同类型元素在内存里依次紧密排列,结构体是不同类型的数据成员在内存里紧密排列,因此二者十分相近,我们可以模仿数组初始化的方法初始化结构体变量:

struct date yesterday={11,15,1994};
struct date tomorrow={.day=22,tomorrow.year=1996};  //出现了.运算符,用于访问成员

printf("month=%d day=%d year=%d\n",yesterday.month,yesterday.day,yesterday.year);
printf("month=%d day=%d year=%d\n",tomorrow.month,tomorrow.day,tomorrow.year);

控制台显示:

month=11 day=15 year=1994
month=0 day=22 year=1996   //可见与稀疏矩阵的赋值一样,未赋值的项会置为0


第四个问题:作为一个变量,结构体可以参与那些运算

不同于数组,结构体不是常量,因此可以参与赋值运算。例如:

struct date yesterday;
struct date tomorrow={.day=22,tomorrow.year=1996}; 

yesterday=tomorrow;  //可以结构体变量之间直接赋值
tomorrow=(struct date){10,1,1949};  //可以将一组数转换为一个结构体变量

printf("month=%d day=%d year=%d\n",yesterday.month,yesterday.day,yesterday.year);
printf("month=%d day=%d year=%d\n",tomorrow.month,tomorrow.day,tomorrow.year);


  对于结构体也可以取指针。它的名字并像数组一样是常量指针了,只是简单的变量名。对于结构体取的指针p,可以用传统的(*p).member来实现对于数据成员的访问,但是C语言有另一种运算->可以访问p所指向结构体的成员

 

  在函数当中使用结构体做形参,实际发生的是形式结合,函数体内形成了一个值相同但是与外界无关的结构体,消耗较大,我们通常传入一个常量结构体指针,达到传入结构体的目标,函数也可以返回结构体

 

第五个问题:结构和数组都是承载基本类型的容器,他们可不可以互相容纳?

  是可以的,我们可以在数组当中放数组,形成二维数组,也可以在结构里放结构,当然也可以混合,结构的成员是数组,或者数组的成员是结构。

 

  以一个长方形数组为例,每个元素是长方形,长方形结构的两个数组成员是两个点,点结构的两个数据成员是x和y两个坐标:

#include <stdio.h> 
typedef	struct point{
		int x;
		int y;
	}Point;   //定义了一个Point结构体

typedef struct rectangle{
	Point p1;
	Point p2;
}Rectangle;   //Rectangle结构体里包含了Point结构体作为其成员

Rectangle* fuzhi(Rectangle *p){
		scanf("%d",&p->p1.x);
		scanf("%d",&p->p1.y);
		scanf("%d",&p->p2.x);
		scanf("%d",&p->p2.y);
		return p;
	}     //
void show(const Rectangle*p){
		printf("p1.x=%d \n",p->p1.x);
		printf("p1.y=%d \n",p->p1.y);
		printf("p2.x=%d \n",p->p2.x);
		printf("p2.y=%d \n",p->p2.y);
}
int main(){

	Rectangle*p;
	Rectangle m[3]={
	{{1,2},{3,4}},   //展现其数组的性质
	{{5,6},{7,8}},    // 我只对两个元素进行赋值 第三个元素像数组一样,值全部置0
	};

	for(int i=0;i<sizeof(m)/sizeof(m[0]);i++){
		printf("第%d个长方形:\n",i);
		show(&m[i]);   
	} 
	printf("------------\n");
	fuzhi(&m[sizeof(m)/sizeof(m[0])-1]);    //调用fuzhi函数对于第三个元素进行赋值
	for(int i=0;i<sizeof(m)/sizeof(m[0]);i++){
		printf("第%d个长方形:\n",i);
		show(&m[i]);	
	} 
	return 0;
}


  控制台输出:

第0个长方形:
p1.x=1
p1.y=2
p2.x=3
p2.y=4
第1个长方形:
p1.x=5
p1.y=6
p2.x=7
p2.y=8
第2个长方形:
p1.x=0
p1.y=0
p2.x=0
p2.y=0
------------
2 3 5 9
第0个长方形:
p1.x=1
p1.y=2
p2.x=3
p2.y=4
第1个长方形:
p1.x=5
p1.y=6
p2.x=7
p2.y=8
第2个长方形:
p1.x=2
p1.y=3
p2.x=5
p2.y=9

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值