本文介绍指针、函数和结构体
粉丝福利, 免费领取C/C++ 开发学习资料包、技术视频/项目代码,1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件,后端开发/音视频开发/Qt开发/游戏开发/Linuxn内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
1、函数
函数定义包含了四个部分:返回类型、函数名、参数列表、函数体。
创建一个函数时,必须指定函数头作为函数定义的第一行,跟着是这个函数放在大括号内的执行代码,称为函数体。函数头定义了函数的名称、形参和返回值类型。一般形式如下:
RetureType FunctionName(Parameters - separated by commas) {
// statements;
}
将大括号及其内存用分号代替,如下所示
RetureType FunctionName(Parameters - separated by commas);
称为函数声明,也称为函数原型。定义了函数的名称、返回值类型和形参列表(参数个数以及顺序)。
参数个数可变的函数
标准库 <stdarg.h> 提供了编写这种函数的工具。在参数列表后添加省略号 ... 可以指定参数个数可变的函数(参数列表中至少需要一个固定的参数)。如下所示:
double average(double v1, double v2, ...) {
// statements;
}
<stdarg.h> 头文件提供了解析可变参数相关的宏定义,分别是 va_start()、va_arg() 和 va_end()。第一个宏的形式如下:
void va_start(va_list parg, last_fixed_arg);
这个宏的名称来源于 variable argument start。这个函数接收两个参数:va_list 类型的指针 parg 和函数指定的最后一个固定参数的名称。va_list 类型也在 <stdarg.h> 头文件中定义,用于存储支持可变参数列表所需的信息。
在调用 va_start() 初始化 parg 后,然后可以调用 va_arg() 函数解析可变参数。va_arg() 的第一个参数是通过调用 va_start() 初始化的变量 parg,第二个参数是期望解析的参数类型。va_arg() 函数返回 parg 指定的当前参数值,同时更新 parg 指针,指向列表的下一个参数。当参数解析完成,va_arg() 函数返回 0。然后调用 va_end() 处理收尾工作,它将 parg 重置为 NULL。如果省掉这个调用,程序就不会正常工作。
例如,average 函数可以定义为:
double average(double v1, double v2, ...) {
va_list parg;
double sum = v1 + v2;
double value = 0.0;
int count = 2;
va_start(parg, v2);
while((value = va_arg(parg, double)) != 0.0) {
sum += value;
++count;
}
va_end(parg);
return sum / count;
}
有时需要多次处理可变参数列表,va_copy() 可以复制 va_list 类型变量 parg。
va_list parg_copy;
va_copy(parg_copy, parg);
2、指针
基本概念
指针也是一种变量,其存储的是其所指变量的地址。如图所示,指针 pnumber 保存另一个变量 number 的地址(起始地址)。
但是只知道变量 pnumber 是一个指针是不够的,更重要的是,编译器必须知道它所指变量的类型。没有这个信息,根本不可能知道所指对象占用多少内存,或者如何处理它所指的内存的内容。每个指针都和某个变量类型相关联,也只能用于指向该类型的变量。
类型名 void 表示没有指定类型,所以 void* 类型的指针可以包含任意类型数据项的地址。类型 void* 常常用做参数类型或者函数返回类型。任意类型的指针都可以传送为 void* 类型的值,在使用它时,再将其转换为合适的类型。
const 与指针
声明指针时,可以使用 const 关键字限定。const 的位置不同,具有不同的含义。
1)const 在 * 号左边,表示指针指向一个常量。
long value = 9999L;
const long *pvalue = &value;
无法通过 pvalue 指针改变 value 的值,比如
*pvalue = 8888L;
会产生编译错误,但是可以改变 pvalue 的值(指向另外的变量)
long number = 8888L;
pvalue = &number;
另外,const 和 long 关键字的顺序没有关系,如下方式也可以声明指向常量的指针:
long const *pvalue = &value;
2)const 在 * 右边,表示指针为常量类型,限定指针的值不能被修改(不能指向其他的变量),如下所示:
int count = 43;
int *const pcount = &count;
数组与指针
数组名可以隐士转换为指针,表示数组的第一个元素的地址。但是数组不是指针,它们有一个重要的区别:可以改变指针包含的地址,但不能改变数组名引用的地址。
另外,数组名代表的并不是数组的地址,尽管两者都指向同一地址,但是指针运算完全不同。比如:
char chs[10];
printf("%x %x\n", chs, &chs);
printf("%x %x\n", chs + 1, &chs + 1);
输出为
0x60fee6 0x60fee6
0x60fee7 0x60fef0
可以看到,chs + 1 指向下一个字节的地址,而 &chs + 1 指向了下 10 个字节的地址。因为 chs 是长度为 10 的字符数组,&chs 类型不再是 char*。可以使用 typedef 简化数组的定义
typedef char TenCharArray[10];
TenCharArray chs;
chs 可以隐式地转换为 char* 指针类型,但是 &chs 为 TenCharArray* 指针类型。
多维数组中,名称和指针之间的差异更为明显,例如定义如下二维数组
char board[3][3] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
};
多维数组和它元素(子数组)的关系如下所示
board 引用第一个元素的地址,该元素仍然是一个数组,而 board[0]、board[1] 和 board[3] 引用对应子数组第一个元素的地址。则用多维数组名称访问元素的方式如下所示
指针数组
数组的元素也可以是指针类型,在声明需要注意方法
char *ptr[10] = {NULL};
上述方法声明了一个容量是 10 个 char* 类型的指针数组。[] 操作符的优先级比 * 高,所以 ptr 首先和 [] 操作符结合,表明 ptr 是数组,然后确定元素类型是 char*。
函数指针
函数也可以看作一种变量,也有其对应的指针类型,使用指针也可以操作函数。函数的内存弟子存储了函数开始执行的位置(起始地址),存储在函数指针中的内容就是这个地址。不过,只有地址还不够,还必须指定形参列表以及返回值类型。
函数指针的定义方法如下:
RetureType (*FunctionPtr)(Parameters - separated by commas);
该声明只定义了一个名为 FunctionPtr 的指针变量,不指向任何内容。*FunctionPtr 必须放在括号中,因为 () 操作符具有最高优先级,FunctionPtr 和右边 () 结合,FunctionPtr 就表示函数。
通过函数指针调用函数和函数调用类型,只需要将函数调用时函数名换成函数指针
int sum(int a, int b);
int (*pfun)(int, int) = sum;
int result = pfun(1, 2);
3、结构体
关键字 struct 能定义各种类型的变量集合,成为结构体,并把它们视为一个单元。下面是一个简单声明一个结构体 Horse 的例子:
struct Horse {
int age;
int height;
} silver = {
27, 12
};
Horse 不是一个变量名,而是一个新的数据类型,定义了一个 Horse 类型的变量 silver 并进行初始化。上面的初始化方式需要将初始值以正确的顺序放在大括号中,也可以在初始化列表中指定成名名(可以只指定部分成员的值),如下:
struct Horse trigger = {
.age = 22, .height = 30
};
结构体内的变量成为成员或字段,通过成员访问运算符 . 访问,例如
silver.age = 12;
不一定要给结构体指定标记符名字,如下例子也是合法的,只不过不能再定义其他变量。
struct {
int age;
int height;
} silver;
同样可以定义指向结构体变量的指针,此时用 -> 运算符访问成员。例如
struct Horse* ptr = &sivler;
ptr->age = 20;
结构体也可以在结构体内部声明,以限制作用域,例如
struct Horse {
struct Date {
int day;
int month;
int year;
} dob;
int age;
int height;
};
位字段机制允许几个成员分别使用某种数据类型的几位,通常是 unsigned int 类型。例如
struct {
unsigned int flag1 : 1;
unsigned int flag2 : 1;
unsigned int flag3 : 2;
unsigned int flag4 : 3;
} indicators;
4、联合体
联合体和结构体类似,但是各个成员以共享的方式占用同一块内存,而不是像结构体一样各个成员独立占用一块内存。关键字 union 用于定义联合体。例如
union U_example {
float decval;
int* pnum;
double my_value;
} ul;
联合体成员的访问方式和结构体成员完全相同,联合体对象所占的字节数是其最大的成员所占的空间。
5、枚举类型
在编程时,常常希望变量存储一组可能值中的一个,枚举就用于这种情形。利用枚举,可以定义一个新的整数类型,该类型变量的值域是指定的几个可能值。下面语句定义了一个枚举类型 Weekday
enum Weekday {Monday, Tuesday, Wednessday, Thursday, Friday, Saturday, Sunday};
可以给任意或者所有枚举器指定特定的整数值。尽管枚举器使用的名称必须唯一,但枚举器的值不要求是惟一的。未设定指定值的枚举器,其值为前一个的值 +1
粉丝福利, 免费领取C/C++ 开发学习资料包、技术视频/项目代码,1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件,后端开发/音视频开发/Qt开发/游戏开发/Linuxn内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓