当前位置: 首页 > news >正文

C 语言从回忆到重识 -- 2 进阶知识之函数、数组与指针

C 语言从回忆到重识 -- 2 进阶知识之函数、数组与指针

    • 9 函数
      • 简单函数
      • 带参数的函数
      • 递归
      • 所有函数皆平等
      • 地址运算符 &
      • 间接运算符 *
      • 指针
      • 声明指针
    • 10 数组和指针
      • 数组
      • 只读数组
      • 未初始化数组的值
      • 部分初始化
      • 自动推算数组长度
      • 指定初始化器(C99)
      • 给数组元素赋值
      • 数组边界
      • 指定数组的大小
      • 多维数组
      • 数组与指针
      • 函数、数组和指针

  • 函数
  • 数组
  • 指针

资料来源:《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){
    // ...
}
可以使用以上提到的任意一种函数原型和函数定义。

相关文章:

  • 关于MS SQL 2005和C#远程连接的详细设置
  • C 语言从回忆到重识 -- 附录
  • C# 3.0语言规范
  • 使用CLion调试redis源码
  • C# 3.0 入门系列(一)
  • C 与 多线程(1)
  • C# 3.0入门(二)
  • 使用weixin-java-miniapp实现微信小程序登录接口
  • Python 下的虚拟环境的使用
  • C#3.0入门系列(三)
  • 一入 Java 深似海 -- S02E02 学习笔记
  • C#3.0入门系列(四)
  • GCC 的简单使用
  • 关于Keil 的一些看法
  • Jackson 简单使用记录
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 07.Android之多媒体问题
  • Babel配置的不完全指南
  • eclipse(luna)创建web工程
  • EOS是什么
  • Java 网络编程(2):UDP 的使用
  • learning koa2.x
  • mysql innodb 索引使用指南
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • Redux 中间件分析
  • 从伪并行的 Python 多线程说起
  • 分享几个不错的工具
  • 适配iPhoneX、iPhoneXs、iPhoneXs Max、iPhoneXr 屏幕尺寸及安全区域
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 阿里云服务器如何修改远程端口?
  • ​iOS安全加固方法及实现
  • ###C语言程序设计-----C语言学习(6)#
  • (C++20) consteval立即函数
  • (pytorch进阶之路)扩散概率模型
  • (九十四)函数和二维数组
  • (三十五)大数据实战——Superset可视化平台搭建
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (转)linux下的时间函数使用
  • (转)Unity3DUnity3D在android下调试
  • (转载)虚函数剖析
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .aanva
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?
  • .net中应用SQL缓存(实例使用)
  • @html.ActionLink的几种参数格式
  • @基于大模型的旅游路线推荐方案
  • [ vulhub漏洞复现篇 ] Hadoop-yarn-RPC 未授权访问漏洞复现
  • [BZOJ1008][HNOI2008]越狱
  • [C#]winform制作仪表盘好用的表盘控件和使用方法
  • [Django 0-1] Core.Checks 模块
  • [Editor]Unity Editor类常用方法
  • [ffmpeg] av_opt_set 解析
  • [hive小技巧]同一份数据多种处理
  • [HTML]Web前端开发技术28(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [linux c]linux do_div() 函数用法