C 语言从回忆到重识 -- 2 进阶知识之函数、数组与指针
C 语言从回忆到重识 -- 2 进阶知识之函数、数组与指针
- 函数
- 数组
- 指针
资料来源:《C Primer Plus 6》
9 函数
简单函数
#include <stdio.h>
// 函数原型
void testFunction(void);
int main() {
// 使用函数
testFunction();
return 0;
}
// 定义函数
void testFunction(void) {
printf("test function ...");
}
带参数的函数
#include <stdio.h>
int add(int, int); /*函数原型*/
int main() {
printf("sum is %d \n", add(3, 544));
return 0;
}
int add(int x, int y) {
return x + y;
}
递归
/**
* 递归 求阶乘
*/
long fact(int x) {
long ans;
if (x < 2) {
ans = 1L;
} else {
ans = x * fact(x - 1);
}
return ans;
}
双递归
unsigned long Fibonacci(unsigned n){
if(n > 2) return Fibonacci(n-1) + Fibonacci(n-2);
else return 1;
}
递归的优缺点
- 优点
- 为某些编程问题提供了最简单的解决方案
- 缺点
- 一些递归算法会快速消耗计算机内存资源
- 不方便阅读和维护
在程序中使用递归需要特别注意,尤其是效率优先的程序;
所有函数皆平等
程序中的每个C函数与其他函数都是平等的,每个函数都可以调用其他函数,或被其他函数调用。
地址运算符 &
指针(pointer)用于存储变量的地址;
一元运算符 &
给出变量的存储地址;
void testPointer(void) {
int pooh = 100;
// &pooh 是变量 pooh 的内存地址
printf("value is %d ,address is %p \n", pooh, &pooh);
testPointer2(pooh);
}
void testPointer2(int temp) {
printf("value is %d ,address is %p \n", temp, &temp);
}
// 值传递
// value is 100 ,address is 0x7ffee803f82c
// value is 100 ,address is 0x7ffee803f80c
间接运算符 *
本质上,指针(pointer)是一个值为内存地址
的变量;
间接运算符 *
,有时也称为解引用运算符
int bah = 100;
ptr = &bah;
val = *ptr; // 找到 ptr 地址中包含的值 100
// 相当于 val = bah;
指针
- 地址运算符 &
- 后跟一个变量名时,&给出该变量的地址
- 地址运算符 *
- 后跟一个指针或地址时,*给出储存在指针指向地址上的值
声明指针
int * pi; // pi 是指向 int 类型变量的指针
char * pc; // pc 是指向 char 类型变量的指针
float * pf; // pf 是指向 float 类型变量的指针
void testPointer3(void) {
int bah = 100;
int *ptr = &bah; // 声明 int 类型指针
printf(" %p \n", &bah);
printf(" %p \n", ptr);
printf(" %d \n", *ptr); // 反向得到指针中的值
}
类型说明符表明了指针所指向对象的类型,星号(*)表明声明的变量是一个指针。
int * pi 声明的意思是 pi 是一个指针,*pi 是 int 类型;
pc 指向的值(*pc)是 char 类型,pc 本身的类型是
指向 char 类型的指针
,pc 的值是一个地址,但是不要把指针认为是整数类型,一些处理整数的操作不能用来处理指针。指针是一个新类型,不是整数类型。
int main() {
int x = 5, y = 9;
printf("x=%d ,y=%d \n", x, y);
testSwapError(x, y);
printf("x=%d ,y=%d \n", x, y);
testSwap(&x, &y);
printf("x=%d ,y=%d \n", x, y);
return 0;
}
// 错误案例
void testSwapError(int x, int y) {
int temp;
temp = x;
x = y;
y = temp;
}
void testSwap(int *x, int *y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
}
10 数组和指针
数组
声明数组
int ints[20];
char chars[50];
初始化
int ints[20] = {1, 2, 3, 4, 5, 6};
char chars[50] = {'g', 'u', "i", 'q'};
// 赋值
ints[6] = 7;
ints[7] = 8;
chars[4] = 'i';
chars[5] = '0';
// 获取值
char x = chars[0]; //数组下标从 0 到 n-1
只读数组
#define MONTHS 12
void test(void){
// 使用 const 声明只读数组
const int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
}
未初始化数组的值
void test2(void) {
int ints[5];
for (int i = 0; i < 10; i++) {
printf("%d \n", ints[i]);
}
}
输出:
1
14
0
0
0
0
-120455107
-476679062
-458512304
32766
Process finished with exit code 0
若数组元素未赋初值,编译器使用的值是内存相应位置上的现有值(所以每次运行结果可能有所不同);
以上结果出现的原因是上述的数组属于自动存储类别(在函数内部声明,且未使用 static 关键字);
int initArray[5];
void test3(void) {
for (int i = 0; i < 10; i++) {
printf("%d \n", initArray[i]);
}
}
// 与 test3 结果相同
void test4(void) {
static int ints[5];
for (int i = 0; i < 10; i++) {
printf("%d \n", ints[i]);
}
}
输出:
0
0
0
0
0
0
0
0
0
0
Process finished with exit code 0
部分初始化
void test5(void) {
int ints[5] = {2, 3, 4};
for (int i = 0; i < 5; i++) {
printf("%d \n", ints[i]);
}
}
输出:
2
3
4
0
0
Process finished with exit code 0
如果部分初始化,剩余的元素会被初始化为 0;
自动推算数组长度
void test6(void) {
int ints[] = {2, 3, 4};
for (int i = 0; i < sizeof ints / sizeof ints[0]; i++) {
printf("%d \n", ints[i]);
}
}
指定初始化器(C99)
// 传统语法
int arr[6] = {0, 0, 0, 0, 0, 212};
// C99 语法
int arr2[6] = {[5]=212};
案例
void test8() {
int arr[6] = {1, [4]=212, 213};
for (int i = 0; i < sizeof arr / sizeof arr[0]; i++) {
printf("%d \n", arr[i]);
}
}
1
0
0
0
212
213
Process finished with exit code 0
下标 1 2 3 位置直接空过去;
给数组元素赋值
C 不允许把数组作为一个单元赋值给另一个数组,除了初始化以外也不允许使用花括号列表形式赋值。
数组边界
int arr[20];
数组下标在 0~19 之间;在使用数组时,要防止下标越界;编译器不会检查出这种错误,在 C 标准中使用越界下标是未定义的,这意味着程序看上去可以运行,但是运行结果很奇怪,或异常中止。
C 语言为何允许这种麻烦事发生?这要归功于 C 信任程序员的原则;
指定数组的大小
int n = 6;
int arr[6];
int arr2[3 + 2];
int arr3[n]; // C99 之前不可用
多维数组
void test10() {
float arr[5][7] = {
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0},
{1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}
};
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 7; j++) {
arr[i][j] = i / (j + 1.0);
}
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 7; j++) {
printf("%f\n", arr[i][j]);
}
printf("-------\n");
}
}
数组与指针
void test11() {
int arr[4] = {1, 2, 3, 4};
printf("%p\n", arr);
for (int i = 0; i < 4; i++) {
printf("%p\n", &arr[i]);
}
}
/* 结果
0x7ffee562f810
0x7ffee562f810 // 数组名是该数组首元素的地址
0x7ffee562f814
0x7ffee562f818
0x7ffee562f81c
*/
数组名是该数组首元素的地址;
- 指针的值是它所指向对象的地址;
- 指针加 1 ,指针的值
递增它所指向类型的大小
(以字节为单位);
函数、数组和指针
需求:编写一个函数,计算一个 int 数组中所有值得和;应该如何调用该函数呢?
是这样吗?
total = sum(marbles);
注意:数组名是该数组首元素的地址
,所以 marbles 是一个存储 int 类型值得地址,应把它赋给一个指针形式参数
,即该形参是一个指向 int 的指针。
#include <stdio.h>
int sum(int *, int);
int sum(int *arr, int length) {
int sum = 0;
for (int i = 0; i < length; i++) {
sum += arr[i];
}
return sum;
}
int main() {
int arr[4] = {1, 45, 3, 4};
int sumValue = sum(arr, 4);
printf("sum is %d\n", sumValue);
return 0;
}
注意 : int *ar 形式和 int ar[] 形式都表示 ar 是一个指向 int 的指针,但是,int ar[] 只能用于声明形式参数。
注意声明数组形参
由于函数原型可以省略参数名,所以下面的原型定义是等价的:
int sum(int *, int);
int sum(int [], int);
int sum(int * ar, int n);
int sum(int ar[], int n);
但是,函数定义不能省略参数名:
int sum(int *ar, int n){
// ...
}
int sum(int ar[], int n){
// ...
}
可以使用以上提到的任意一种函数原型和函数定义。