7/25函数后部分:作用域以及其变量 以及乌班图linux多文件操作方法

以下是对这组 C 语言笔记核心知识的总结与补充,围绕变量作用域、生存期、存储类型数组传参展开,帮你梳理清晰 C 语言变量管理的底层逻辑:

一、变量的作用域与可见性

1. 作用域分类
  • 局部作用域:花括号 {} 包裹的范围(函数体、代码块),变量仅在该范围内有效,出了范围“不可见”。
  • 全局作用域:花括号外部的变量(文件顶部定义),默认在整个程序可见(跨文件需 extern 配合 )。
  • 函数形参作用域:局限于函数体,等同局部变量。
2. 可见性规则(命名冲突处理)
  • 先定义后使用:用变量前必须定义/声明,否则编译器报错。
  • 无嵌套作用域:同名不干扰:两个独立作用域(如不同函数)的同名变量,互不影响。
  • 同作用域:同名不允许:同一作用域(如单个函数、单个文件全局区),不能重复定义同名变量(类型不同也不行 )。
  • 嵌套作用域:内层屏蔽外层:嵌套作用域(如函数内嵌套代码块)定义同名变量时,外层变量被“屏蔽” ,优先用内层;退出内层后,外层恢复可见。

示例补充

// 全局作用域
int i = 10;  
void func() {
    // 内层作用域,屏蔽全局 i
    int i = 20;  
    printf("%d", i); // 输出 20
}
int main() {
    func();
    // 全局 i 恢复可见
    printf("%d", i); // 输出 10 
    return 0;
}
#include <stdio.h>

// 全局变量
int global_var = 100;  

void func() {
    // 局部变量,屏蔽全局同名变量
    int global_var = 200; 
    printf("函数内局部 global_var:%d\n", global_var); // 输出 200
}

int main() {
    printf("main 中全局 global_var:%d\n", global_var); // 输出 100
    func();
    // 离开 func 后,局部变量消失,恢复全局变量
    printf("main 中再次访问全局 global_var:%d\n", global_var); // 输出 100
    return 0;
}

二、变量的生存期(“活多久” + 存储管理 )

变量生存期是从分配内存释放内存的周期,决定变量存在时长,分两类:

1. 静态生存期
  • 适用变量:全局变量、static 修饰的局部变量(静态局部变量 )。
  • 存储区域:程序运行前(编译/链接阶段 ),在静态存储区(全局区 )分配内存;程序结束时释放。
  • 特性
    • 全局变量:作用域默认覆盖整个程序(跨文件用 extern 扩展 )。
    • 静态局部变量:static 修饰后,作用域仍局限于当前函数/代码块,但生存期与程序同周期;多次进入作用域时,值会保留(“记忆性” )。

示例补充

void count() {
    // 静态局部变量,仅初始化一次
    static int num = 0;  
    num++;
    printf("%d ", num); 
}
int main() {
    count(); // 输出 1
    count(); // 输出 2(保留上次值)
    count(); // 输出 3
    return 0;
}

三:两种变量:

1. 全局变量(静态存储区,程序周期存活 )

#include <stdio.h>

// 全局变量,未手动初始化,编译器自动赋 0
int global_uninit;  

int main() {
    printf("全局未初始化变量:%d\n", global_uninit); // 输出 0
    return 0;
}

说明:全局变量存在静态存储区,未初始化时自动赋 0(区别于局部变量未初始化是随机值 )。

静态局部变量(static 修饰,“记忆性” )

#include <stdio.h>

void count() {
    // 静态局部变量,程序运行前初始化,仅执行 1 次
    static int num = 0; 
    num++;
    printf("静态局部变量 num:%d\n", num);
}

int main() {
    count(); // 输出 1
    count(); // 输出 2(保留上次值)
    count(); // 输出 3
    return 0;
}

说明:static 修饰局部变量后,生存期变为程序周期(存在静态存储区 ),作用域仍局限于函数内,但值会 “记住” 上次结果。

static+全局变量:

限制变量进作用于本文件

2. 动态生存期
  • 适用变量:普通局部变量(未用 static 修饰 )、动态分配内存(malloc 等 )的变量。
  • 存储区域:进入作用域(函数调用、代码块执行 )时,在栈区分配内存;退出作用域时,自动释放内存(动态分配需手动 free )。
  • 特性:作用域与生存期一致,离开作用域后变量“消失”,值无法保留。

动态生存期(普通局部变量 )

#include <stdio.h>

void func() {
    // 普通局部变量,进入函数时分配栈内存,离开销毁
    int local_var = 10; 
    printf("函数内局部变量:%d\n", local_var); // 输出 10
}

int main() {
    func();
    // 错误:local_var 是 func 的局部变量,main 作用域不可见
    // printf("%d", local_var); 
    return 0;
}

说明:普通局部变量存在栈区,作用域和生存期一致(函数执行期间 ),离开函数后变量销毁,外部无法访问。

四、特殊变量修饰(register

  • register 变量
    • 意图:请求编译器将变量存到CPU寄存器(而非内存 ),加速访问(适合频繁用的变量,如循环计数器 )。
    • 特性:
      • 编译器可忽略请求(寄存器不足或变量不适合时 )。
      • 无法取地址(& 操作 ),因为寄存器不是内存地址。
      • 现代编译器优化强,手动加 register 必要性降低,但理解原理仍有用。

示例

// 建议存寄存器(编译器决定是否采纳)
register int i;  
for (i = 0; i < 1000000; i++); // 频繁使用,适合寄存器
// &i; // 报错!无法取寄存器变量地址
#include <stdio.h>

int main() {
    // 请求编译器将 i 存寄存器(实际由编译器决定)
    register int i; 
    // 模拟频繁使用:循环计数
    for (i = 0; i < 5; i++) { 
        printf("register 变量 i:%d\n", i); 
    }
    // 错误:寄存器变量无法取地址
    // &i; 
    return 0;
}

说明:
register 是 “请求”,编译器可能忽略(比如变量要取地址时,必然存在内存 )。
现代编译器优化强,即使不加 register,也可能自动将频繁使用的变量放寄存器,但理解语法仍有意义。

五、二维数组传参(C语言数组传参细节 )

  • 传参语法:二维数组传参时,第二维长度必须显式写(第一维可省略 ),因为数组在内存是“连续线性存储”,需列数算偏移。
    示例函数声明:
    // rows:行数,第二维 [4] 显式指定列数
    void printArray2D(int arr[][4], int rows) {  
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < 4; j++) {
                printf("%d ", arr[i][j]);
            }
            printf("\n");
        }
    }
    
  • 传参本质:数组名退化为“指向数组首元素的指针”,传参实际传地址;因此,函数内无法用 sizeof 获数组真实长度(需手动传行数、列数 )。
#include <stdio.h>

// 二维数组传参:第二维长度必须显式写(如 [4])
void printArray2D(int arr[][4], int rows) { 
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    // 定义二维数组:3 行 4 列
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    // 传参:行数 3,第二维 [4] 匹配函数声明
    printArray2D(arr, 3); 
    return 0;
}

输出结果:

1 2 3 4 
5 6 7 8 
9 10 11 12 

说明:
二维数组传参时,第二维长度必须固定(函数声明写 [4] ),因为数组在内存是连续存储,需列数计算偏移。
函数内无法通过 sizeof(arr) 获取原数组长度(传参后退化为指针 ),需手动传行数 rows。

六、命名冲突(不同作用域同名变量 )

c
运行
#include <stdio.h>

// 全局作用域变量
int var = 100; 

void func1() {
    // 局部作用域同名变量,屏蔽全局
    int var = 10; 
    printf("func1 内 var:%d\n", var); // 输出 10
}

void func2() {
    // 另一个局部作用域,同名不影响 func1
    int var = 20; 
    printf("func2 内 var:%d\n", var); // 输出 20
}

int main() {
    printf("main 中全局 var:%d\n", var); // 输出 100
    func1();
    func2();
    return 0;
}
输出:
plaintext
main 中全局 var:100
func1 内 var:10
func2 内 var:20

说明:不同作用域的同名变量互不干扰(func1 和 func2 的 var 是独立局部变量 ),全局变量可被局部作用域屏蔽。

如何使用ubuntu的终端编辑项目:

一个项目可以由多个.c文件编写函数,每个.c文件创建对应的头文件(.h文件)

使用乌班图linux终端创建多个文件:

vsp + 文件名+ .c

在这里插入图片描述

.c文件:创建函数。

打开新窗口后解决无法操作两个窗口的问题:

set mouse = a

在这里插入图片描述

从而让鼠标能操作多个窗口。

创建头文件(.h文件)

“sp” + ".c 文件对应的文件名 “+ " .h”

在这里插入图片描述

三种文件输入要求:

在(.c)文件中创建函数:

在这里插入图片描述

在头文件(.h)文件中声明要调用的函数:

在这里插入图片描述

在函数声明前加extern使头文件更加规范化。

强调函数的外部链接属性

C/C++ 中的函数默认具有外部链接(即可以被其他源文件访问),extern 关键字在这里主要是显式地强调这一点,让代码意图更清晰。例如:

c
运行
// math.h 头文件
extern int add(int a, int b); // 声明外部函数add

即使省略 extern,函数声明默认也是外部的,但加上 extern 可以让读者明确知道该函数的定义在其他文件中。

主函数格式:

在有头文件的情况下无需声明要调用的函数,引入头文件格式:

在这里插入图片描述

static+全局变量:

限制变量进作用于本文件

课上代码:

第一部分:求二维整型数组的外围元素之和

#include<stdio.h>
//求二维整型数组的外围元素之和:
int sumOFEdgeArray2D(int a[][4],int rows)
{
    int sum=0;
    int i,j;
    for(i=0;i<rows;++i)  // 遍历每一行
    {
        for(j=0;j<4;++j)  // 遍历每行的4个元素
        {
            // 判断是否为外围元素:第一行、最后一行、第一列或最后一列
            if(i==0||j==0||i==rows-1||j==3)
            {
                sum+=a[i][j];  // 累加外围元素值
            }
        }
    }
    return sum;  // 返回总和
}
int main(void)
{
    // 定义一个3行4列的二维数组
    int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
    // 计算数组的行数:总字节数/一行的字节数
    int rows=sizeof(a)/sizeof(a[0]);
    // 调用函数并输出结果
    printf("%d\n",sumOFEdgeArray2D(a,rows));
    return 0;
}

这段代码功能是计算二维数组外围元素的总和。对于3行4列的数组[[1,2,3,4],[5,6,7,8],[9,10,11,12]],外围元素是:1,2,3,4,5,8,9,10,11,12,总和为65。

第二部分:二维整型数组的行元素翻转

//二维整型数组的行元素翻转:
void reverse2D(int a[][4],int rows)
{
    int i,j;
    for(i=0;i<rows;++i)  // 遍历每一行
    {
        // 每行只需要交换一半元素即可完成翻转
        for(j=0;j<4/2;++j)
        {
            // 交换第j个和第(4-1-j)个元素
            int t=a[i][j];
            a[i][j]=a[i][4-1-j];
            a[i][4-1-j]=t;
        }
    }
}

// 打印二维数组
void printarray2D(int a[][4],int rows)
{
    int i,j;
    for(i=0;i<rows;++i)
    {
        for(j=0;j<4;++j)
        {
            printf("%2d ",a[i][j]);  // 格式化输出,占2个字符位置
        }
        printf("\n");  // 每行结束换行
    }
}
int main(void)
{
    int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
    int rows=sizeof(a)/sizeof(a[0]);
    reverse2D(a,rows);  // 调用翻转函数
    printarray2D(a,rows);  // 打印翻转后的数组
    return 0;
}

这段代码实现了二维数组每行元素的翻转。原数组每行[1,2,3,4]会变成[4,3,2,1],以此类推。

第三部分:优化版二维数组行翻转(函数复用)

// 翻转一维数组的函数
void reverse(int a[],int len)
{
    int i;
    for(i=0;i<len/2;++i)
    {
        // 交换第i个和第(len-i-1)个元素
        int t=a[i];
        a[i]=a[len-i-1];
        a[len-i-1]=t;
    }
}

// 二维数组行翻转(调用一维翻转函数)
void reverse2D(int a[][4],int rows,int len)
{
    int i;
    // 对每一行调用一维数组翻转函数
    for(i=0;i<rows;++i)
    {
        reverse(a[i],len);
    }
}

// 打印二维数组(同前)
void printarray2D(int a[][4],int rows)
{
    int i,j;
    for(i=0;i<rows;++i)
    {
        for(j=0;j<4;++j)
        {
            printf("%2d ",a[i][j]);
        }
        printf("\n");
    }
}
int main(void)
{
    int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
    // 计算列数:一行的总字节数/一个元素的字节数
    int len=sizeof(a[0])/sizeof(a[0][0]);
    int rows=sizeof(a)/sizeof(a[0]);
    reverse2D(a,rows,len);  // 调用翻转函数
    printarray2D(a,rows);
    return 0;
}

这是第二部分的优化版本,将一维数组的翻转功能封装成独立函数reverse(),然后在reverse2D()中调用,实现了代码复用,更符合模块化设计思想。

第四部分:字符串数组排序

#include<string.h>  // 包含字符串处理函数库

// 打印字符串数组
void printStrings(char s[][100],int rows)
{
    int i;
    for(i=0;i<rows;++i)
    {
        puts(s[i]);  // 输出字符串并换行
    }
}

// 对字符串数组进行排序(冒泡排序)
void sortStrings(char s[][100],int rows)
{
    int i,j;
    for(i=0;i<rows-1;++i)
    {
        for(j=i+1;j<rows;++j)
        {
            // strcmp返回值>0表示s[i]大于s[j]
            if(strcmp(s[i],s[j])>0)
            {
                // 交换两个字符串
                char t[100];
                strcpy(t,s[i]);
                strcpy(s[i],s[j]);
                strcpy(s[j],t);
            }
        }
    }
}
int main(void)
{
    char s[][100]={"hello","world","china"};  // 字符串数组
    int rows=sizeof(s)/sizeof(s[0]);  // 计算字符串个数
    sortStrings(s,rows);  // 排序
    printStrings(s,rows);  // 打印排序结果
    return 0;
}

这段代码实现了字符串数组的排序功能,使用strcmp()比较字符串大小,使用strcpy()复制字符串,采用冒泡排序算法。对于输入{"hello","world","china"},排序后结果为china, hello, world(按字母顺序)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值