C++入门基础教程:C语言的指针与结构体到底怎么用?

Posted Zhi Zhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++入门基础教程:C语言的指针与结构体到底怎么用?相关的知识,希望对你有一定的参考价值。

目录

一、前言

二、指针

2.1 指针与地址

2.2 指针与函数参数

2.3 指针与数组

2.3.1 指针与一维数组

2.3.2 指针与二维数组

2.4 字符指针与函数

2.5 指针数组以及指向指针的指针

2.5.1 指针数组

2.5.2 指向指针的指针

2.6 指向函数的指针

三、结构体

3.1 结构体的基础知识

3.1.1 结构体变量的定义

3.1.2 结构体变量的初始化

3.1.3 访问结构体变量成员

3.2 结构体与函数

3.3 结构体数组

3.4 指向结构体的指针

3.5 类型定义(typedef)

结束语


一、前言

 

C++是C语言的继承,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计,因而C++就适应的问题规模而论,大小由之。C++不仅拥有计算机高效运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。

博主通过对C++基础知识的总结,有望写出深入浅出的C++基础教程专栏,并分享给大家阅读,今后的一段时间我将持续更新C++入门系列博文,想学习C++的朋友可以关注我,希望大家有所收获。指针是C语言的精髓和灵魂,学会使用C指针会让大家在C++的学习中游刃有余;同时,为了熟悉和理解C++中的类,理解并熟练使用C语言中的结构体也是非常有必要的!

下面我们将开启C语言中指针和结构体的学习,内容比较多,大家如果短时间学不完可以先收藏!

二、指针

指针是一种存放变量地址的变量,不同类型的指针变量所占用的存储单元长度是相同的(相同系统),例如:在32位系统上指针占用4个字节,而在64位系统上则占用8个字节(指针变量占用内存大小与其类型无关)。

2.1 指针与地址

指针的定义:数据类型 * 指针变量名;

int * p;     /* p是一个指向整型数据类型的指针 */

 一元运算符用于取一个对象的地址,定义一个整型变量 a,取它的地址,再赋值给指针向量 p,此时 p 是指向 a 的指针。p的值为变量 a 在内存中的地址,即指针 p 存放变量 a 的地址。

int a = 10;
p = &a;

注意:地址运算符  & 只能应用于内存中的对象,即变量和数组元素。它不能作用于表达式、常量或 register类型的变量。

指针的使用

一元运算符 * 是间接寻址或间接引用运算符,当它作用于指针时,将访问指针所指的对象。

例 1:
int x = 1, y = 2, z[10];
int * p;                    /* p 是一个指向整型数据类型的指针 */
p = &x;                     /* p 指向x */
y = *p;                     /* 将 p 访问到的内容赋值给y */
*p = 10;                    /* 修改 p 所指向对象的内容 */
p = &z[0];                  /* 改变 p 所指向的对象 */

在例1中,语句 p = &x; 将指针 p 指向 x, *p则获取了变量 x 的内容,此时  *p等于1,执行语句 y = *p; 之后,变量 y 的值也为1。

指针还可以间接修改其指向变量的值,语句 *p = 10;是将10赋值给指向 p 指向对象的内容。因为指针 p 指向 x,变量x的值是1,执行语句 *p = 10;之后,变量 x 的值为10。

语句p = &z[0]; 将指针 p 指向数组 z[10] 的第一个元素,即指针 p 由指向变量x 改为指向数组z[10]。 

2.2 指针与函数参数

函数的参数分为形参和实参两种。形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。

例2:函数参数传递,交换元素的次序。

#include<stdio.h>

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

void test()
{
	int a = 10, b = 20;
	swap(a, b);
}

int main()
{
	test();
	return 0;
}

上述代码并没有实现交换元素的功能,被调函数 swap 中参数 x 和 y 的交换并不会改变主调函数中参数a和b的值,该函数是从实参 a 和 b 处拷贝了一份副本,仅仅对其副本的值进行交换。

C语言中函数的实参与形参之间的传递方式是单向的"值传递",只能将实参传递给形参,反之不行。被调用函数的形参只有函数被调用时才会临时分配存储单元,一旦调用结束,占用的内存便会被释放。

利用指针传递的方式可以改变主调函数中参数a和b的值。

swap(&a, &b);

由于一元运算符&用来取变量的地址,因此 &a 和 &b就是分别指向变量 a 和 变量b 的指针,利用这些指针可以间接访问它们指向的操作数。

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

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

指针参数使得被调用函数能够访问和修改主调函数中对象的值。

2.3 指针与数组

2.3.1 指针与一维数组

在C语言中,指针和数组之间的关系十分密切,通过数组下标所能完成的任何操作都可以通过指针来实现。一般来说,用指针编写的程序比用数组下标编写的程序执行速度快,但另一方面,用指针实现的程序理解起来稍微困难一些。

int a[10];
int *pa;
pa = &a[0];

定义了一个长度为10的数组a和一个指向整型对象的指针pa,赋值语句pa = &a[0];将指针pa指向数组a的第一个元素。如果pa指向数组中的某个特定元素,那么,根据指针运算的定义,pa+1将指向下一个元素,pa+i将指向pa所指向数组元素之后的第i个元素,而pa-i将指向pa所指向数组元素之前的第 i个元素。因此,如果指针 pa 指向 a[0],那么*(pa+1)获取的是数组元素a[1]的内容,pa+i是数组元素a[i]的地址,*(pa+i)获取的是数组元素a[i]的内容。

因为数组名所代表的就是该数组最开始的一个元素的地址,所以赋值语句 pa=&a[0] 也可以写成 pa=a; 此时 pa 与 a 的值相同。对数组元素 a[i] 的引用也可以写成 *(a+i) 这种形式。

a 代表的是数组a[10]的首地址,即元素a[0]的地址,执行语句 pa=a; 则是将指针 pa 指向数组a[10]的首地址a[0]。

*(pa+i)获取的是数组元素a[i]的内容, *(a+i) 也可以获取数组元素a[i]的内容。

结论:1)&a[i] 等价于 a+i ,是 a 之后第 i 个元素的地址;2)pa[i] 等价于 *(pa+i) ;3)一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。

注意:指针是变量,在C语言中,语句pa=a; 和 pa++; 都是合法的。但数组名不是变量,类似于a=pa; 和 a++; 形式的语句是非法的。

2.3.2 指针与二维数组

int a[3][4];   /* a 是一个二维数组,有3行4列 */

 对于一个二维数组a,可以将数组a 看作只有3个元素a[0]、a[1]、a[2]组成的一维数组。a[i] (0<=i <=2)是数组名,代表每一行的首地址,也就是第i 行第 0 列的元素地址,则a[i]+j 代表元素a[i][j]的地址,即&a[i][j]。

二维数组 a[m][n] 的地址及元素表示形式和含义
表示形式含义
a,&a[i]

二维数组名,数组起始地址,

是第0行起始地址,指向一维数组a[0]

a[0],*a,

*(a+0),

&a[0][0]

第0行第0列元素的地址

a[i],

*(a+i)

&a[i][0]

第 i 行第0列元素的地址
a+i,&a[i]第 i 行起始地址

a[i]+j,*(a+i)+j,

&a[i][j]

第 i 行第 j 列元素的地址

* ( a[i]+j ),*( *(a+i)+j),

* (&a[0][0]+i*n +j ),a[i][j]

第 i 行第 j 列元素的值

2.4 字符指针与函数

字符串常量是一个字符数组,例如:“I am a strinng”。

由于在字符串的内部表示中,字符数组以空字符'\\0'结尾,所以字符串常量占据的存储单元比双引号内的字符数大1。

下面两个定义之间有很大的差别:

char *pmessage="I am a student";     /* 定义一个指针 */
char amessage[]="I am a student";    /* 定义一个数组 */

pmessage是一个指向字符串常量的指针,它可以被修改以指向其他地址,但是字符串的内容是不能修改的;而 amessage是一个存放初始化字符串以及空字符'\\0'的字符数组,数组中的单个字符可以修改,但是 amessage始终指向同一个存储位置。 

例3:分别使用数组方法和指针方法实现 strcpy函数,strcpy(s, t)函数的作用是将指针 t 指向的字符串复制到指针 s 指向的位置。

/* 数组方法实现 */
void strcpy(char *s, char *t)
{
	int i=0;
	while( (s[i]=t[i]) !='\\0')
		i++;
}

/* 指针方法实现:版本1 */
void strcpy(char *s, char *t)
{
	while( (*s = *t) !='\\0')
	{
		s++;
		t++;
	}
}

因为参数是通过值传递的,所以在strcpy函数中可以以任何方式使用参数 s 和 t。在此,s 和 t 是方便地进行了初始化的指针,循环每执行一次,它们就沿着相应的数组前进一个字符,直到将 t 中的结束符'\\0'复制到 s 为止。

经验丰富的程序员更喜欢将它编写程成下列的形式。

/* 指针方法实现:版本2 */
void strcpy(char *s, char *t)
{
	while( (*s++ = *t++) !='\\0');
}

由于++和 -- 既可以作为前缀运算符,也可以作为后缀运算符,所以还可以将运算符 * 与运算符++和 -- 按照其它方式组合使用,但这些用法并不多见。

*--p;   /* 在读取指针p指向的字符之前先对p执行自减运算 */

进栈和出栈的标准用法
*p++ = val;  /* 将val压入栈 */
val=*--p;    /* 将栈顶元素弹出到val中 */

2.5 指针数组以及指向指针的指针

2.5.1 指针数组

若数组中的每个元素都是指针类型,用于存放内存地址,则这个数组就是指针数组。

定义:类型说明符 *指针数组名[数组长度]

例如:int *a[10];

指针数组通常用于处理二维数组和多个字符串。

// 指针数组的初始化
char *ps[]={"Fortran","Visual C++","NULL"};   
int arr[3][3]={1,2,3,4,5,6,7,8,9};
int *parr[3]={arr[0],arr[1],arr[2]};

例4:有多个字符串,求其中最大的字符串。

#include<stdio.h>
#include<string.h>

int main()
{
	char *str[]={"Fortran","Visual C++","Basic"};
	int n=3;
	int i,Maxindex=0;
	for(i=1;i<n;i++)
		if(strcmp( str[i],str[Maxindex])>0)
			Maxindex=i;
	printf("最大的字符串是:%s\\n",str[Maxindex]);
	return 0;
}

2.5.2 指向指针的指针

指向指针的指针称为二级指针变量,它可以存放指针变量的地址,定义二级指针变量如下:

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

int x=10,*p,**pp;
p=&x;         // 一级指针p指向整型变量x
pp=&p;       // 二级指针pp指向一级指针p
*p=20;       // 一级间接访问,等于x
**pp=50;     // 二级间接访问,即*(*pp),等于x
变量x、一级指针p与二级指针之间的关系

x、*p和**pp代表了同一内存单元,它们的值是10;p和*pp代表同一内存单元,它们的值是&x;pp、&p和&&x等价。

2.6 指向函数的指针

在C语言中,一个程序执行时它的每个函数都会占用内存中一段连续的区域,每个区域都有一个入口地址,一个函数的函数名就代表了该函数的入口地址,即函数名表示的是函数指针常量。从而可以定义一个指针变量接收函数的入口地址,使指针变量指向函数,这就是指向函数的指针,也称函数指针。通过函数指针可以调用函数,还可以作为函数的参数。

定义:类型说明符 (*指针变量名) (形参类型表);

通过函数指针调用函数:函数指针也是变量,在使用前必须赋值。由于函数名就代表了该函数的入口地址,因此可以将一个函数名直接赋值给指向函数的指针变量,但该函数必须已经定义或声明过,并且函数指针的类型与该函数返回值的类型必须一致。

例如:
int (*funp) (int, int);  // 定义一个函数指针
int min(int a,int b);
funp=min;    // funp指向函数min

例5:输入3个数,输出最大数。

#include<stdio.h>

int max(int,int);
int main()
{
	int a,b,c,dmax;
	int (*funp) (int, int);  // 定义指向返回值为整型的函数指针,参数为两个整型变量
	
	printf("请输入3个数:");
	scanf("%d%d%d",&a,&b,&c);

	funp=max;    // funp指向函数max

	/* 用函数名和函数指针分别调用函数max */
	dmax=max(c,(*funp)(a,b));

	printf("最大值为%d\\n",dmax);

	return 0;
}

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

注意:在定义指向函数的指针变量时,变量名外的括号不能缺少。

int *funp();     // 说明了一个函数,其返回值为指向整型的指针
int (*funp)();   // 说明了一个指向返回值为整型的函数指针

三、结构体

结构体是C语言中一种重要的数据类型,该数据类型由一组称为成员(或称为域,或称为元素)的不同数据组成,其中每个成员可以具有不同的类型。结构体通常用来表示类型不同但是又相关的若干数据。在C语言中,可以定义结构体类型,将多个相关的变量包装成为一个整体使用。在结构体中的变量,可以是相同、部分相同,或完全不同的数据类型。在C语言中,结构体不能包含函数。在面向对象的程序设计中,对象具有状态(属性)和行为,状态保存在成员变量中,行为通过成员方法(函数)来实现。C语言中的结构体只能描述一个对象的状态,不能描述一个对象的行为。在C++中,考虑到C语言到C++语言过渡的连续性,对结构体进行了扩展,C++的结构体可以包含函数,这样,C++的结构体也具有的功能,与class不同的是,结构体包含的函数默认为public,而不是private。

3.1 结构体的基础知识

3.1.1 结构体变量的定义

定义结构体类型的变量有3种方法。

(1)先声明结构体类型,再定义结构体类型的变量。

struct student
{
	int num;
	char name[20];
	int score;
};
struct student s1,s2;   // 定义了2个结构体类型的变量s1和s2

(2)声明结构体类型的同时,定义结构体类型的变量。

struct student
{
	int num;
	char name[20];
	int score;
}s1,s2;

(3)省略类型名,直接定义结构体类型的变量。

struct
{
	int num;
	char name[20];
	int score;
}s1,s2;

3.1.2 结构体变量的初始化

struct student
{
	int num;
	char name[20];
	int score;
};
struct student s1={1,"张三",20};

3.1.3 访问结构体变量成员

访问结构体变量成员的一般形式:结构体变量 . 成员

(1)C语言规定不能对一个结构体变量整体进行输入输出操作,只能对结构体变量具有的成员进行输入输出操作。

输出 s1 的各成员的值,应该写为:printf("%d,%d,%d",s1.num,s1.name,s1.score);

(2)同类型的结构体变量可以整体赋值,如:s2=s1;

(3)结构体变量的成员可以像普通变量一样,进行赋值、运算等操作。

s1.score=70;   s2.score=40; sum=s1.score+s2.score;

(4)两个结构体变量不能用运算符==或!= 进行比较操作。

3.2 结构体与函数

(1)结构体变量做实参时,形参也必须是同类型的结构体变量,函数调用传递的是实参结构体的完整结构,即将整个结构体成员的内容全部按顺序复制给形参,在函数调用期间形参也需要另外开辟一段内存空间来存储从实参传递过来的各成员的值。

(2)被调函数对形参的任何操作都是对局部变量的操作,不会影响到主调函数实参变量的值。

(3)函数调用中,结构体变量可以作为函数返回值。

例6:输入一个注册日期,如2021年5月20日,修改改日期,按照如下格式输出“2021-06-07”。

#include<stdio.h>

// 结构体
struct data
{
	int year;
	int month;
	int day;
};

// 结构体类型的函数
struct data fun(struct data t)
{
	t.year=2021;
	t.month=6;
	t.day=7;

	return t;
}

int main()
{
	struct data d;
	scanf("%d%d%d",&d.year,&d.month,&d.day);
	d=fun(d);
	printf("修改后的日期为:%d-%02d-%02d\\n",d.year,d.month,d.day);
	return 0;
}

3.3 结构体数组

定义结构体数组:

struct 结构体名

{

成员列表;

};

struct 结构体名 数组名[数组长度];

// 结构体
struct student
{
	int num;
	char name[20];
	float score;
};
// 结构体数组
struct student stu[40];

定义一个结构体数组 stu,共有40个元素,stu[0]~stu[39],每个数组元素都包括结构体的3个成员。

引用结构体数组成员的方式:结构体数组名[下标].成员名

例如:stu[2].num=10;  stu[2].name="李四"; stu[2].score=87;

3.4 指向结构体的指针

结构体指针变量的定义:结构体类型说明符 *结构体指针变量名;

struct student s1,*p;  // 定义结构体变量s1和结构体指针变量p

p=&s1;                 // 结构体指针 p 指向结构体变量s1

 结构体指针变量访问结构的成员:(*结构体指针变量).成员名  或  结构体指针变量->成员名

说明:

(1)(*p)表示p 指向结构体变量,(*p).name代表p 所指结构体变量中的成员name。“.”的优先级大于“ * ”,所以*p两侧的括号不能少。

(2)“->”的优先级最高,结构体指针只能指向结构体类型的变量,不能指向其成员。

① p->num;   得到p所指结构体变量成员num的值。

② p->num++;   得到p所指结构体变量成员num的值,之后使该值加1。

③ ++p->num;   先使p所指结构体变量成员num的值加1,再使用它。

(3)若结构体指针 p 已指向结构体变量 x ,则下面3种表示结构体成员的形式是完全等效的。

① x.成员名;

② (*p).成员名;

③ p->成员名;

(4)一个结构体不能包含它本身类型的成员变量,但是却可以包含指向本身类型的指针成员。

// 结构体
struct student
{
	int num;
	char name[20];
	float score;
    struct student *p;
};

3.5 类型定义(typedef)

typedef是C语言的关键字,用于为已经存在的数据类型定义一个新的类型名,实质就是起别名。

typedef 原类型名 新类型名;

定义一个类型名代表一个结构体类型

// 结构体
typedef struct student
{
	int num;
	char name[20];
	float score;
}STU;
// 定义结构体变量
STU stu1,stu2;

结束语

大家的点赞和关注是博主最大的动力,博主所有博文中的代码文件都可分享给您(除了少量付费资源),如果您想要获取博文中的完整代码文件,可通过C币或积分下载,没有C币或积分的朋友可在关注、点赞和评论博文后,私信发送您的邮箱,我会在第一时间发送给您。博主后面会有更多的分享,敬请关注哦!
 

以上是关于C++入门基础教程:C语言的指针与结构体到底怎么用?的主要内容,如果未能解决你的问题,请参考以下文章

C语言结构体指针定义问题 - C / C++ -

C#如何调用C++的DLL的结构体数组指针

C++结构体指针数组如何分配空间,用new

黑马程序员 C++教程从0到1入门编程笔记1数据类型运算符程序流程结构数组函数指针结构体

C语言指针

C++编程基础: 13. 结构体与联合体