C 语言基础 -- 函数/指针/结构体

本文介绍指针、函数和结构体

粉丝福利, 免费领取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内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值