找回密码
 立即注册
首页 业界区 业界 C语言基础-cnblog

C语言基础-cnblog

椎蕊 2025-12-25 05:15:00
软考

代码托管网站 github gitee  分支控制器

计算机基础


目录

  • 软考
  • 代码托管网站 github gitee  分支控制器
  • 计算机基础

    • 基础知识
    • 存储器
    • 原码补码反码
    • 计算机输入输出缓冲区设置原理+刷新缓冲区的方式

  • C语言

    • 概述
    • 程序的入口和结束
    • 函数调用
    • 关键字和标识符

      • 标识符
      • 关键字
      • (1) 字符型
      • (2) 整数型
      • (3) 短整型
      • (4) 长整型
      • (5) 长长整型
      • (6) 浮点型
      • (7) 字符串
      • (8) 布尔型

    • 输入输出函数

      • 格式化输出


  • o 把八进制的前导符输出
  • x 把十六进制的前导符输出



      • 格式化输入

    • 运算符

      • 算数运算符
      • 位操作运算符
      • 关系运算符
      • 逻辑运算符
      • 条件运算符
      • 赋值运算符
      • 逗号运算符
      • 常见的优先级

    • C语言的语句和块

      • 复合语句
      • 表达式语句
      • 选择语句
      • 标签语句
      • 跳转语句
      • 迭代语句

    • C语言的数组

      • 数组的概念
      • 数组的定义
      • 数组的访问

        • 重点

      • 数组初始化

        • 重点

          • (1) bzero()
          • (2) memset()


      • 字符型数组
      • 数组型数组
      • 柔性型数组
      • 匿名型数组
      • 零长度数组

    • C语言的指针



        • 指针的概念
        • 指针的定义



  • define NULL        ((void *)0)





        • 数组指针

          • 常用的优先级
          • 数组指针的访问


      • 指针数组
      • 二级指针
      • 习题

    • C语言的函数

      • 函数定义

        • 耦合内聚

      • 函数调用
      • 函数参数
      • 单向传递
      • 双向传递
      • 生命周期
      • 作用范围
      • 数组传递

        • static

      • 内存分布

        • (1) 保留区
        • (2) 代码段
        • (3) 数据段
        • (4) 堆空间
        • (5) 栈空间
        • 习题:内存分析重点


    • 日志
    • const常量关键字



        • (1) int *const p;
        • (2) const int *p;


    • 递归思想的应用
    • C语言的结构体

      • 基本概念
      • 成员访问
      • 初始化
      • 空间大小
      • 变量定义
      • 别名定义
      • 函数传参

    • C语言联合体

      • 基本概念
      • 成员访问

    • C语言的枚举
    • C语言的宏定义

      • 基本概念
      • 定义格式
      • 具体分类

        • 字符串

      • 作用范围

    • 程序的编译过程


基础知识

计算机组成五部分组成:运算器、控制器、存储器(内存、外存)、输入设备、输出设备
内存:常见的内存条
外存:常见的硬盘
CPU只于内存打交道
内存与外存两者的区别:
内存的数据是掉电会丢失
外存的数据是掉电不会丢失
计算机之父:冯诺依曼    图灵奖(纪念艾伦·图灵)
冯诺依曼提出的理论:

  • 提出计算机是由五部分组成
  • 计算机中的数据采用二进制存储
电子元件具备双稳定性

  • 计算机中的程序按照顺序组成
1946年 第一台电子计算机
存储器

查看数电第七章半导体存储
计算机存储多字节数据时,涉及大端小端问题
存储器分为内存外存
内存:常见的内存条
外存:常见的硬盘
CPU只于内存打交道
半导体存储器:存储大量的二进制信息的半导体
  1.                    从存取功能来看分为只读存储器**ROM**和随机存储器**RAM**
复制代码
随机存储器:随机读/写存储器
随机是指随时想读哪个地址就读取,想写入哪个地址就写
ROM:掉电不会丢失        属于非易失性存储器
RAM:掉电会丢失        属于易失性存储器
随机存储器又分为:静态随机存储器SRAM和动态随机存储器DRAM
SRAM:制作工艺复杂,内存小
DRAM:制作简单,容量大,常用作内存条
DRAM:是利用MOS管栅极电容可以存储电荷原理
电容具备充电放电的特性,充电到一定值时表示逻辑1,放电到一定值时表示为逻辑
计算机处理数据的最小单位:bit
计算机处理数据的基本单位:byte
32位系统和64位系统
指的是计算机地址总线的位数,指的是计算机的寻址范围
32位系统
32bit=4GB
32bit也就是32个电容,32个0或1
$$
2^{32}=4GB
$$
写地址时,32个地址太长了,采用十六进制表示
32个二进制==8个十六进制
存储单元地址:0x0000_0000=0xFFFF_FFFF
每个存储单元大小都是1字节
每个存储单元的地址长度都是4字节
1字节=8bit
4字节=32bit
64位系统
存储单元地址长度为8字节
存储单元的大小不变1字节
只是地址编号变成了
0x00000000_00000000-0xFFFFFFFF_FFFFFFFF
原码补码反码

计算机内部的二进制都是由补码的方式来存储数据
二进制负数的补码等于它的反码加1
反码计算时,保留符号位不变
正数的原码反码补码相同
负数:函数中不特意声明unsigned
就是有符号,在地址最高位,8位数据=1位符号位+7位数据位
最高位0 为正 1为负
127=原码:0 111_1111        反码:0 111_1111        补码:0 111_1111
129=原码:127+1+1+1=0 111_1111+1+1
  1. =1 000_0000+1=1 000_0001反码:1 111_1110补码:1 111_1111=-127
复制代码
出现数据溢出现象
输入的数据超出了存储器申请的空间大小,会导致数据溢出,数据异常
计算机输入输出缓冲区设置原理+刷新缓冲区的方式

计算机的CPU的性能好,所以CPU的执行效率高,但是计算机的输入设备和输出设备的效率低,所以CPU为了提高工作效率,要降低访问输入输出设备的次数。

缓冲区(Buffer)是内存空间的一部分。也就是说,在内存中预留了一定的存储空间,用来暂时保存输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区输出缓冲区。
缓冲区分为:输入缓冲区和输出缓冲区
缓冲区一般4kb
CPU读取内存的方式:  1、'\n'换行符是信号的一种
  1.                                 2、缓冲区数据已满时,也会通知CPU                                3、程序死掉时,也会通知CPU
复制代码
根据IO设备的不同,可以把缓冲区分为输入缓冲区和输出缓冲区,同样,根据刷新形式的不同,可以把缓冲区分为三种:全缓冲、行缓冲、无缓冲
*全缓冲:*指的是当缓冲区被填满就立即把数据冲刷到文件、或者在关闭文件、读取文件内容以及修改缓冲区类型时也会立即把数据冲刷到文件,一般读写文件的时候会采用
*无缓冲:*指的是没有缓冲区,直接输出,一般linux系统的标准出错stderr就是采用无缓冲,  这样可以把错误信息直接输出。
*行缓冲:*指的是当缓冲区被填满(一般缓冲区为4KB,就是4096字节)或者缓冲区中遇到换行符’\n’时,或者在关闭文件、读取文件内容以及修改缓冲区类型时也会立即把数据冲刷到文件中,一般操作IO设备时会采用,比如printf函数就是采用行缓冲。
思考:如果用户不小心在使用printf函数输出字符串的时候忘了添加结束标志’\n’,并且用户没有打算输出太多字符,请问此时要输出的字符串会不会输出?
回答:可以输出,当程序结束时会自动刷新缓冲区,如果输入缓存区中存在数据则被丢弃,如果输出缓存区存在数据,则会输出。
思考:如果用户不小心在使用printf函数输出字符串的时候忘了添加结束标志’\n’,并且用户没有打算输出太多字符,并且程序永远不退出,请问此时要输出的字符串会不会输出?

回答:是不会输出,因为不满足刷新条件,注意:用户可以选择手动刷新缓冲区,可以调用标准C库中的fflush()函数接口
第一个f指的文件
flsh刷新


注意:不要利用fflush函数区刷新输入缓冲区(stdin) ,因为一般编译器是不支持该操作!!!
查找:查阅资料,了解输入输出缓存区为什么一般是4KB大小? 提示:涉及到linux的内存分页机制。
C语言

概述

c语言是由ANSI 美国标准协会组织发布,c语言也称为ANSIC
后面由ISO组织国际标准组织制定与发布C语言
C标准一共四套:
知道C89、C99、C11就可以
主流的编译器都支持C99
写程序是给人看的,写程序要避免二义性
写程序是给人看的,机器是看不懂的,所以就得使用编译器将C语言转换为机器看的懂得二进制指令0和1组成
GCC编译器编译:
  1. gcc demo.c -o xxx   
复制代码
字符是组成语言的基本元素
C语言中的字符都是英文字符,由美国国家标准协会ASO设计,收录128个字符
标准ASCII码是128个字符        2的七次方        七个电容
拓展ASCII码是256个字符        2的八次方        八个电容=8bit        每一个字符用8bit,也是8个电容(连续的电容)
存储器一般以一组电容(8bit)为存储单元
只需要记住常见的三个:
字符0:十进制 48
字符A:十进制 65
字符a:十进制 97
大写字母与小写字母之间差32
笔试题:把大写字母与小写字母进行转化
中国也有自己的标准GBK2312 这个是中文简体字符集
后来推出GBK收录了繁体中文
UTF-8字符集是收录了各个国家的字符
最后养成编译习惯
写程序都要有注释,程序是给人看的
写文件、函数、算法、都要注释
要养成一个好的编译习惯
查看The Linux Kernel Archives
程序的入口和结束

查看文档C99第5章第6章
1、程序开始
程序启动时,有一个main的函数被调用
main函数必须要有返回值,可以没有参数
2种main函数


2、程序结束
  1. int main()   {                return 0;   }
复制代码


exit函数只能调用一次,可以让程序正常终止
系统自动调用exit函数,并且将main的返回值传递给exit函数作为参数
0正常终止、非零为异常
函数调用

函数调用分为主函数和子函数
主函数就是main
子函数分为库函数用户自定义
库函数:标准库        官方机构发布的
  1.   第三方库        其他组织
复制代码
主要是如何区分哪个是官方的哪个是自己的


< xxx.h > :编译器只去系统指定路径中查找该头文件,如果未找到,则编译器进行错误提示。
“ xxx.h ” :先去当前路径中查找头文件,未找到再去系统路径中查找,还未找到则报错提示。
怎么掌握一个函数

关键字和标识符

标识符

存储器单元分为常量和变量常量和变量统称为标识符
"量"是指存储单元
"常和变"指的是数据的状态是否发生变化
  1. int a=10;10是常量、a是变量10是int型占4个字节,int型的a也申请了4个字节    一共申请了8个字节   
复制代码
标识符:只能由字母、数字、下划线、以及美元符号组成
第一个字符只能是字母和下划线
统一域内不能重复定义,不能使用系统已经定义的名字
关键字


C语言标准中常用的关键字*只有32个*
(1) 字符型

C语言标准中提供了一个关键字char,其实是character单词的缩写,表示字符的意思,操作系统char类型的数据宽度定义为1字节,用于存储字符,C语言标准中用单撇号’ ’表示字符。

C语言标准规定:用户打算存储字符  数据宽度  变量名称 ;  举例: char  ch  =  ‘c’ ;
C语言标准中关于字符的种类有两种:*普通字符* and *转义字符*,对于ASCII码表中转义字符

*注意:ASCII码中的转义字符需要使用* *‘**\0**’* *‘**\r**’* *‘**\n**’**进行表示,代表字符具有特殊的意义。*
思考:已知计算机是以二进制存储数据,意味着写入到存储器中的字符都会被编译器转换为而二进制指令,那请问用户能否直接以二进制指令的形式把字符输入到存储器中,编译时是否会报错??
回答:会报错,因为C语言不支持二进制输入,但是C语言支持八进制、十进制、十六进制。
(2) 整数型

C语言标准中使用关键字int表示整数,关键字int的英文单词是integer,对应的中文具有整数的含义,在32系统下关键字int的数据宽度是4字节,也就意味着存储单元所能存储的整数范围比较广泛。


C语言标准规定了用户可以采用不同的进制来表示数据,常用的进制有八进制、十进制以及十六进制,并且为了区分这三种进制,所以规定每种进制都有对应的前导符(前缀),规定八进制的前缀以0表示,比如064,规定十六进制的前缀以0x/0X表示,比如0x64。
(3) 短整型

C语言标准中规定使用关键字short来表示短整型,一般短整型的全称是short int,只不过写程序的时候可以只写short即可,在32位系统下short短整型占2字节。

(4) 长整型

C语言标准中规定使用关键字long来表示长整型,一般长整型的全称是long int,只不过写程序的时候可以只写long即可,在32位系统下long长整型占4字节,在64位系统占8字节。

(5) 长长整型

C语言标准中规定使用关键字long来表示长整型,但是长长整型是使用long long来表示,在32位和64位系统下长长整型占8字节。

思考:C语言中提供了char、int、short、long、long long来表示整型,但是整型数是分正负的,编译器如何来区分整数的正负呢?
回答:C语言标准中提供了两个关键字 signed  &&  unsigned用于修饰整数,unsigned表示无符号,signed表示有符号,C语言中的signed修饰符是隐式声明,也就是用户定义整型变量的时候如果没有特别强调,则整型变量默认是有符号的。
用户如果要存储无符号的整数,则必须定义变量必须显式声明变量是无符号的(unsigned)

思考:计算机可以区分整数的正负,但是程序最终都会被转换为二进制指令,但是二进制是不分正负的,请问编译器是如何转换数据的???
回答:二进制可以分正负,在二进制数的前面添加1bit,作为符号位,并且bit=0,用于表示二进制数为正数,bit=1,用于表示二进制数为负数。
*注意:设计程序时,定义的变量如果是有符号的,则尽量避免写入超过变量范围的数值!!!!!!*
字符型: char  1字节 -- 有符号 -- 数值范围 -128 ~ 127  --  无符号 -- 数值范围 0 ~ 255
短整型:short  2字节 -- 有符号 -- 数值范围 -32768 ~ 32767 无符号 --数值范围 0 ~ 0 ~ 6553565
(6) 浮点型

数据有整数和小数之分,一般情况下处理的数据也是具有小数的,所以C语言标准中规定使用关键字float来表示单精度浮点数,单精度浮点型占4字节,另外C语言标准中也提供了另一个关键字double用来表示双精度浮点数,double占8字节(64位),其实C语言也提供了一种类型long double,该类型占16字节
C语言中一般表示浮点数有两种方案:十进制形式 or 指数形式,两者的具体区分如下所示
l 十进制形式
十进制形式是采用整数+小数的组合表示浮点数,比如3.14 、5.0 ,基本上也是最常用的方案。
l 指数形式
指数形式指的是采用以10为底的形式表示浮点数,比如 3.14 × 10² ,但是在编写程序的时候采用英文半角输入法进行设计,这种输入法是不支持输入上标或者下标,所以C语言规定采用字符e或者E来表示以10为底的指数,比如3.14E2 。
*注意:C语言标准中规定字符e/E的后面必须是整数,另外字符e/E的前面必须有数字!!!!!!*
思考:用户定义了一个单精度浮点型变量,并把变量命名为a,此时用户不小心把一个整数5存储到了该变量中,请问变量中存储的数是什么? float a;  回答:%f  5.000000
作业:整型数据在计算机中存储的时候是以二进制补码形式进行存储,请问一个浮点型数据在计算机中是如何存储的?
浮点型数据(如 C 语言的float、double)的存储逻辑和整型完全不同 ——不采用补码,而是遵循「IEEE 754 标准」(全球通用的浮点存储规范),核心思想是用「科学计数法的二进制形式」存储,拆解为 3 个部分:符号位、指数位、尾数位,通过这三部分协同表示小数和大数。
(7) 字符串

字符串是表示某种含义的一个字符序列,字符串在内存是需要一块连续的内存空间进行存储,C语言中规定字符串使用*双引号*表示,并且规定字符串的结束标志是*’**\0**’*,但’\0’不需要用户手动添加,系统会自动在一个字符串的末尾添加’\0’。
思考:既然在内存中字符串需要一块连续的空间来存储,内核肯定会返回字符串的开始地址,请问内核如何判断字符串何时结束?  回答:遇到’\0’自动结束

(8) 布尔型

用户有时候需要在程序进行判断,在C89标准中,用户如果想要判断某种条件是否成立,一般是定义一个整型变量,然后利用数字0和数字1来表示条件是否成立,用户就可以把变量作为一个标志位使用。
在C99标准可以使用布尔型来表示真假两种情况,头文件stdbool.h有相关描述,在头文件中定义了三个宏,分别是bool、true以及false。

用1不一定是真,但是用true一定是1
同理

思考:大家已经知道基本数据类型的宽度是取决于系统环境以及编译器,假设用户此时并不清楚当前使用的机器的位数(32bit/64bit)和编译器类型,那应该如何知道基本数据类型的宽度?
使用sizeof函数

输入输出函数

一、C语言的输入输出
用户如果打算使用标准C库的函数,就必须要包含函数库对应的头文件,比如输入输出函数对应的头文件就叫做stdio.h,stdio指的是standard input output,翻译为标准输入输出。在linux系统下stdio.h的位置在 */usr/include*/stdio.h
格式化输出

一般标准C库中提供了很多关于输出的函数接口,其中最常用的就是printf()函数,关于printf函数的使用规则可以通过linux系统的man手册进行查阅。

用户一般都是使用linux系统,在linux系统中是提供了一个帮助文档,叫做man手册,man手册一共有9个章节,只需要打开linux系统的终端(Ctrl+Alt+T)输入命令:
  1. man  man
复制代码
man man
第一个man查找,第二个man是,man手册
man手册至关重要

如果想要针对性查找某些接口,可以指定man手册章节,终端输入:
  1. man  n(1-9)  xxx
复制代码



stdout 标准输出,默认设备是显示器

字符串用" "表示
**char **一般表示字符串,如果想要表示字符就不加
在printf函数中" "是格 式化字符串
普通字符是" "中没有%的
%转换说明符
(1) 标志说明

#把对应进制的前导符进行输出
o 把八进制的前导符输出

x 把十六进制的前导符输出

必须把#放在转换说明符%的后面
-减号用于数据对齐,表示左对齐
0表示高位不足时补零
%08x 格式控制符,其中:

  • 0 表示高位不足时补零;
  • 8 表示输出宽度为 8 位;
  • x 表示以小写十六进制输出
默认的是右对齐
  1. printf("%-10d%d\n",100,200);
复制代码
(2) 字段宽度
字段宽度是指待输出的字符串需要占用多少字符
  1. printf("%*d\n",10,20);*在格式化字符串中可以理解为占位符        表示列宽        此时函数第二个参数必须是整数,提供给* 在这里10作为第二个参数给*
复制代码
(3) 转换精度

精度用英文句号.表示
.后面跟一个可选的十进制
如果只有.,则精度为0
会进行四舍五入
(4) 长度修饰

h:half(一半)
hh:把整型转换为字符型,只是针对输出内容而言,数据本身没有受到影响
h:把整型转换为短整型 ,只是针对输出内容而言,数据本身没有受到影响
注意:计算机内部存储多字节的数据时会涉及到大端小端,不同的处理器架构采用的模式是不同的,一般X86架构采用小端模式,ARM架构一般采用大端模式(但是并不绝对)!!!!!!

多字节数据
小端存储:数据的低地址存储在内存的低地址
大端存储:数据的高地址存储在内存的低地址
笔试题:设计程序,判断当前机器是大端还是小端
  1. printf("%hhx\n",0x12345678)
复制代码
(5) 转换说明

f默认精度为6位(采用四舍五入)
%f        单精度
%lf        双精度

%c        以字符形式输出
%s        以字符串形式输出

%p        可以把存储单元的编号以十六进制形式输出
  1. printf("%p\n",&a)    &取地址符,&变量名--->得到变量地址--->得到变量对应的存储单元的编号a='h';        相当于访问变量a对应的存储单元*(unsigned int *)0x75ffc448='e';0x75ffc448合法地址(unsigned int *)指向无符号整数的指针第一个*是解引用运算符,作用是 “通过指针读取或修改它指向的内存中的数据”。
复制代码
普通符号%,需要写%%
两个百分号,表示%
(6) 转义字符
在利用printf函数输出字符串时,用户可能需要输出一些转义字符\n’以及’\t’是比较常用的。
注意:有的时候是需要把一些特殊字符当做普通字符输出,比较 “” ,可以把 \” 和 \” 输出。
(7) 返回结果

函数调用成功,则返回打印的字符个数,'\0'不算在内
因为遇到'\0'就退出
笔试题:请分析以下程序,根据自己对程序的理解回答出程序的运行效果,不许讨论和抄袭!
printf是标准输出:输出到显示器
printf是返回结果:输出字符的个数
结果为:4321
思考:程序为什么是下面的运行效果?同一个程序,就加了一个换行,为什么效果不一样  回答:从计算机组成原理的角度  工作效率问题
查看计算机基础的输入输出缓冲区设置原理

第一个程序,是5s后出现helloworld
原理:从第一句函数开始分析
首先、判断有没有满一行(4096字节),而hello是5字节,显然没有满一行,所以不会刷新(显示器看不到)
其次、判断程序有没有结束,程序没有结束,也不会刷新
最后、判断程序有没有'\n',没有'\n'的信号,也不满足刷新条件
hello还是放到缓存区中
延时5s
同理,输入的world也被放到与hello相同的缓存区
最终,程序结束,缓存区中有数据,就会自动刷新,所以显示器会在5s后看到helloworld
第二个程序,是先显示hello,5s后出现world
回答:计算机的CPU的性能好,所以CPU的执行效率高,但是计算机的输入设备和输出设备的效率低,所以CPU为了提高工作效率,要降低访问输入输出设备的次数。

格式化输入

用户会使用标准输入设备(键盘)写入数据,所以标准C库提供了一个输入函数scanf(),作用是把数据流写入到内存中,scanf指的是scan format,翻译为格式化扫描,用法类似于printf函数。



注意:如果在scanf的字符串中使用了多个转换说明符,则输入的数据类型必须个转换类型一一对应。
不要在设计程序时调用scanf函数,在scanf内部的字符串中手动添加'\n'
常用的:%d        整型                        %f        单精度浮点型
  1.                   %s        匹配字符串(遇到'\0')结束                %c        字符
复制代码
  1. scanf("%c%d%f",&ch,&a.&b);在程序运行时,输入时用空格给分开    例如:    h 100 3.1415
复制代码
(1) 匹配字符
需要匹配的字符序列应该由[]括着
如果[^],表示不匹配[]中的字符
-在[]中可以当作连接符
[0-9]匹配0-9,只有输入这10个数字有效的
[^0-9]不匹配0-9,检测到其中一个就结束输入
  1. scanf("%[^#]s",buf);[]匹配字符[^]不匹配对应字符    在这个代码中,[^#]时,不匹配#,当遇到#时,就停止输入。但是刷新缓冲区还是要
复制代码
运算符


11种赋值运算符
运算符指明要进行的运算和操作,操作数是指运算符的操作对象,根据运算符操作数的数目不同,C语言标准把运算符分为三种:单目运算符(一元运算符)、双目运算符(二元运算符)、三目运算符(三元运算符)。
单目就是一个操作对象
双目就是两个操作对象
算数运算符

C语言中提供了7种算术运算符,分别是 +  -  *  /  %  ++  -- ,++和--都属于单目运算符,使用的时候比较特殊,其他五种都属于双目运算符。
+、-、*、/  就是四则运算
当+作为正号时,是单目运算符
除法运算符两端的操作数都是整数的时候,得到的结果也是整数,当两个操作数无法被整数,会舍弃小数,只保留整数,不进行四舍五入
除法运算符两端的操作数不全是整数,比如其中一个操作数是浮点数,则得到的结果也是浮点数,如果不指定精度,则默认是6位精度

C语言中提供%作为求余运算符,也可以称为取模运算符,C语言标准中规定%符号两端的操作数必须是整数
可以知道,C语言中的运算符是有优先级的,运算符的优先级指的是多个运算符出现在同一个表达式中,优先执行哪个运算符,可以知道算术运算符*** / %的优先级是高于算术运算符+和-**。
思考:既然运算符有优先级,那如果一个表达式使用的运算符的优先级都一样,那应该如何进行运算?
C语言中的运算符是有结合性的,运算符的结合性指的是多个优先级相同的运算符出现在同一个表达式中,优先执行哪个运算符。
运算符的结合性分为两种:一种是左结合性,遵循先左后右原则,比如 a + b - c,相当于(a+b)-c,另一种是右结合性,遵循先右后左原则,比如双目运算符中的赋值运算符 = ,比如表达式a=b=c,相当于a=(b=c)。
*注意:C语言中的单目运算符和三目运算符都是遵循右结合性,当然也包含双目运算符中的赋值运算符=,其他的运算符都遵循左结合性。*

C语言中提供了两个增量运算符++和--,属于单目运算符,*只能在变量中使用*,一般可以作为变量的后缀增量或者后缀减量,++和--也被称为自加运算符和自减运算符,注意:C语言标准中规定++和--也可以作为变量的前缀增量前缀减量。 作用是让变量的值自动加1或者减1。
a++ :在表达式中则表示先让变量a参与表达式运算,运算之后才会让变量a的值自加
++a :在表达式中则表示先让变量a的值自加,自加之后再参与表达式运算
核心思想自增 / 自减运算符的作用是修改变量,但它们的返回值是一个临时的、不可修改的数值。 这个返回值只能被使用,不能被赋值。因此,任何试图将 a++ 或 ++a 的结果作为赋值目标的操作,在 C 语言中都是语法错误。
int a=10;
int b=1;
a = ++a+b;
=运算级最低
单目运算符的优先级比+高
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

掌握好前缀和后缀运算规则、结合性
思考:前缀增量和后缀增量都属于单目运算符,如果一个表达式中同时出现两种运算符,那应该如何进行解释?  比如 表达式 ++i++ 如何解释?   ++(i++)  遵循右结合性
单目运算符都是右结合性
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

++i+:单目运算符的优先级高于+,属于前缀增量 3+1=4 i=4
++i:4+1=5 i=5
sizeof输出数据类型的大小
选C
注意sizeof在C语言中是一个操作符,作用是计算数据类型的大小,结果以字节为单位,sizeof括号中的表达式是不会实现运算和处理的。

思考:为什么输出都是4,为什么两个char输出为4?
另外,虽然sizeof运算符中的表达式不会被运算,但是如果sizeof表达式中出现多个数据类型的大小计算,这个时候会涉及到C语言基本数据类型的转换,转换的意思指的是将数据(变量、数值、表达式的结果等)从一种类型转换为另一种类型。
一般程序中的数据类型转换分为两种情况:自动类型转换 or 强制类型转换。两者区别如下

  • 自动类型转换
自动类型转换就是编译器默默地、隐式地进行的数据类型转换,这种转换不需要程序员干预,会自动发生。比如将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换。
在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型转换为左边变量的类型,这可能会导致数据失真,或者精度降低;所以说自动类型转换并不一定是安全的。对于不安全的类型转换,编译器一般会给出警告。
在不同类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。
转换的规则:转换按数据长度增加的方向进行,以保证数值不失真,或者精度不降低。例如int 和 long 参与运算时,先把 int 类型的数据转成 long 类型后再进行运算。所有的浮点运算都是以双精度进行的,即使运算中只有 float 类型,也要先转换为 double 类型,才能进行运算。另外char 和 short 参与运算时,必须先转换成 int 类型。
  1. int a=0x1234568;char b=0x01;char c=0x01;a=a+b+c;//0x12345678+0x00000001+0x00000001=0x1234567a先将char转化为int型
复制代码

  • 强制类型转换
自动类型转换是编译器根据代码的上下文环境自行判断的结果,有时候并不是那么“智能”,不能满足所有的需求。如果需要,程序员也可以自己在代码中明确地提出要进行类型转换,这称为强制类型转换。
自动类型转换是编译器默默地、隐式地进行的一种类型转换,不需要在代码中体现出来;强制类型转换是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。换句话说,自动类型转换不需要程序员干预,强制类型转换必须有程序员干预。
强制转换规则:  (需要转换的数据类型)  变量          ()常量
不会四舍五入
总结:无论是自动类型转换还是、强制类型转换,都只是为了本次运算而进行的*临时性转换*,转换的结果也会保存到临时的内存空间(栈空间),不会改变数据本来的类型或者值。
位操作运算符

C语言中提供了6种位操作运算符,分别是  ~  &  ^  |   >,其中~属于单目运算符,其他五种都属于双目运算符。
查看数字电路第二章,掌握符号
~:按位取反,对于二进制数而言,0变为1,1变为0  ~ 1101_1110 = 0010_0001
&:按位与,对于二进制而言,当两个bit同时为1,则结果为1,如果bit存在0,结果为0
|:按位或,对于二进制而言,当两个bit同时为0,则结果为0,如果bit存在1,结果为1
^:按位异或,对于二进制而言,当两个bit相同,则结果为0,两个bit不同,则结果为1
> 3 -- 0000 1111
优先级可以查看C语言标准手册(优先级从高到低排序)&:按位与,对于二进制而言,当两个bit同时为1,则结果为1,如果bit存在0,结果为0
|:按位或,对于二进制而言,当两个bit同时为0,则结果为0,如果bit存在1,结果为1
^:按位异或,对于二进制而言,当两个bit相同,则结果为0,两个bit不同,则结果为1
> 3 -- 0000 1111
优先级:可以查看C语言标准手册(优先级从高到低排序)

  • 单目位运算符:~(按位取反),属于单目运算符,优先级较高。
  • 移位运算符:(右移)。
  • 按位与:&。
  • 按位异或:^。
  • 按位或:|。
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

先换成二进制做
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。
笔试题:


关系运算符

C语言中一共提供了6种关系运算符,分别是 <    >=  ==  != ,关系运算符都是双目运算符,都遵循左结合性,一般用在条件判断中,如果表达式中使用关系运算符,则表达式也被称为关系表达式,关系表达式的结果只有两种,用户可以使用布尔型进行结果的表示。




通过标准可以知道,关系运算符 <    >=的优先级相同的,并且这四种运算符是高于关系运算符 ==  !=,两者的优先级是相同的。
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

逻辑运算符

C语言中提供了3种逻辑运算符,分别是 &&  ||   ! ,对于逻辑与&&和逻辑或||都属于双目运算符,遵循左结合性而逻辑非!属于单目运算符,遵循右结合性。一般表达式中如果使用了逻辑运算符,则表达式被称为逻辑表达式。

潜规则:如果一个操作对象的逻辑为假,则不会继续分析第二个操作对象的逻辑
非零即真,两个操作对象有一个为假,则整个表达式的结果为假

潜规则:如果第一个操作对象的逻辑为真,则不会分析第二个操作对象的逻辑
两个操作对象有一个为真,则整个表达式结果为真
还有一个逻辑运算符是 !  (逻辑非) ,属于一元运算符,只有一个操作对象,遵循右结合性,作用是把操作对象的逻辑取反(真变为假,假变为真)。
优先级:逻辑的优先级最高>算数运算符>关系运算符>逻辑与>逻辑或
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

条件运算符

C语言中提供了1种条件运算符,符号是 ? : ,条件运算符是唯一的一个三目运算符,需要三个操作数。

赋值运算符

C语言中提供了11种赋值运算符,如下图所示,都属于双目运算符,但是遵循右结合性!!!


  • 左值(lvalue):本质是 “可寻址、可标识的实体”(比如变量名),能出现在赋值运算符的左侧(比如 a = 5 中,a 是左值,因为它有内存地址,能被赋值)。
  • 右值(rvalue):本质是 “一个临时的、不可寻址的值”(比如字面量 5、表达式计算结果 a+3),只能出现在赋值运算符的右侧(比如 b = a+3 中,a+3 是右值,它是临时结果,没有独立内存地址,不能被赋值)。
让我们来分解 a++ 这个操作,假设 a 的初始值是 5。

  • 读取变量 a 的值:计算机去 a 所在的内存地址,读取到它的值是 5。
  • 返回一个临时值:计算机创建一个临时的、看不见的空间,把刚刚读取到的 5 放了进去。这个 5 就是 a++ 这个表达式的 结果
  • 修改变量 a 的值:计算机回到 a 原来的内存地址,把它的值改成 6。
关键结论:当你写下 (a++) 时,你得到的是那个 临时的 5,而不是变量 a 本身。这个临时的 5 是一个没有固定地址的 右值
当你试图执行 (a++) += a 时,它在计算机看来就像是在执行:5 += 6; (假设第二步后 a 变成了 6)
这显然是荒谬的,你不能把 6 加到 5 这个数字上。+= 的左边必须是一个可以被修改的 容器,也就是变量。
逗号运算符

C语言中提供了1种逗号运算符,符号是 ,  作用是把多个表达式连在一起,构成一个大的表达式,也被称为逗号表达式。注意按照从左向右的流程对每个表达式进行运算,只是逗号表达式最终结果是最后一个表达式的结果。

思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

B
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。

7

思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。
做错了

(3/2)        ---->        得到1        因为都是整数相除
(int) 1.99*2        ---->        得到2        先进行强制转化(不进行四舍五入),再乘2
思考:请问该笔试题的结果是什么?请给出简单的推理过程,请独立完成该笔试题的分析。


常见的优先级

优先级层级运算符类型代表运算符1括号(最高)()2算术运算符+、-、*、/3关系运算符、==、!=4逻辑运算符&&、`5赋值运算符(最低)=、+=、-=*注意:C语言中的单目运算符和三目运算符都是遵循右结合性,当然也包含双目运算符中的赋值运算符=,其他的运算符都遵循左结合性。*
C语言的语句和块

C语言程序的基本单位:函数        函数是由语句构成        函数都需要配合复合语句使用
C语言标准中一共提供6种语句,注意C语言中的语句要指明执行的操作,并且没有特殊情况,语句是按照顺序执行的。

用户一般会把实现某些功能的语句整合在一起,构成一个语法单元,C语言标准的语法单元也被称为块,也被称为块语句。
复合语句

复合语句可以限制语句的作用范围,但是一般情况复合语句很少单独使用,都是和其他语句一起使用使用。

表达式语句

C语言程序一般使用表达式来实现某种操作,表达式是由一组操作符以及操作数组成,目的是实现某种特定的操作。


选择语句


(1) 针对一种情况
C语言标准中提供了一种 if() 语句,if是C语言的关键字之一,具有“如果”的含义,可以看到if()语句需要一个控制表达式,当表达式为真时,则会执行statement,如果表达式为假,则不执行statement。



(2) 针对两种情况
C语言中提供了if()... else结构的语句,else是C语言关键字之一,具有“否则”,当if语句的表达式不成立时,则会执行else对应的语句。

if ( 表达式 )
{
块语句1; //当表达式为真,则执行块语句1
}
else
{
块语句2;   //当表达式为假,则执行块语句2
}
(3) 针对多种情况
C语言标准提供了switch语句,switch也是C语言关键字之一,用于表示多分支选择,需要配合标签语句一起用:
switch( 表达式 )  //表达式结果必须是整型
{
case  常量表达式1  :  待执行的语句1
case  常量表达式2  :  待执行的语句2
case  常量表达式3  :  待执行的语句3
case  常量表达式4  :  待执行的语句4
default      :  待执行的语句n ---->当所有的case语句都不满足时才会执行!!!
}
标签语句

C语言标准中提供了3种标签语句,其中使用频率较高是case标签和default标签,case和default都是C语言的关键字之一,case具有匹配的含义,default具有默认的含义。

case  常量表达式1  :  待执行的语句1
case  常量表达式2  :  待执行的语句2
case  常量表达式3  :  待执行的语句3
case  常量表达式4  :  待执行的语句4
default     :  待执行的语句n  ----->当所有的case语句都不满足时才会执行!!!
注意:case标签的常量表达式的结果必须是整型常量,并且case标签必须是互斥的(不能出现重复的情况,会导致二义性)。
注意:case标签语句和default标签语句只能在switch语句中使用,对于普通的标签语句是可以在跳转语句中使用的。
注意:switch语句应该结合break语句,目的是利用break语句终止switch语句,避免多个标签都被执行。


switch( 表达式 )  //表达式结果必须是整型
{
case  常量表达式1  :  { 待执行的语句1 ..... }  break;
case  常量表达式2  :  { 待执行的语句2 ..... }  break;
case  常量表达式3  :  { 待执行的语句3 ..... }  break;
case  常量表达式4  :  { 待执行的语句4 ..... }  break;
default      :  待执行的语句n ---->当所有的case语句都不满足时才会执行!!!
}
笔试题:遇到switch时,看有没有break语句
避免多个标签被执行
笔试题:float比较
  1. float        a=3.14;if(a==3.04)        {        printf("==\n");        }else{printf("!=\n");}输出:!=原因一个数是浮点型,一个是整数,精度不一样
复制代码
跳转语句

C语言标准中提供了四种跳转语句:goto语句、continue语句、break语句、return语句。


continue只能用于循环体中
跳到循环体的末尾,相当于提前结束本次循环(一次的循环)
循环体内continue后面的语句不会被执行
没有终止循环




迭代语句

C语言标准中提供了3种迭代语句给用户完成重复性的工作,迭代也可以理解为循环,可以重复执行某个代码块。

非零即真
可以看到,C语言标准中提供了while()语句、do ...while()语句、for()语句,每种语句都具有控制表达式,当控制表达式的值不等于0则会重复执行循环体,当控制表达式的值等于0时终止循环体的执行。
(1) while()语句
先判断,后执行


(2) do...while()语句
先执行,后判断


(3) for()语句
先判断,后执行

表达式2如果被省略,则会用非零常量0进行替换
for( ; ; )是死循环
  1. for( int i=0 ;i= a && b = c && b         得到地址下面的值</p>[align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163052145-1918445676.jpg[/img][/align]
  2. 笔试:
  3. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163051642-670004502.jpg[/img][/align]
  4. 运行不报错
  5. 可以知道,[b]如果E1是数组名,E2是整型常量,则E1[E2]可以等价于 E2[E1][/b],这两种方式都可以访问数组中的元素。
  6. 思考:如果用户定义了一个整型数组 int buf[5]; 那么 (buf+1) 指的是[b]数组地址 向后偏移一个元素对应的单元大小,也就是地址向后偏移了4字节[/b],请问 ([b]&buf+1[/b]) 表示什么意思,应该如何解释?
  7. 回答:(&buf + 1)表达式中存在地址运算符,&可以是一元运算符,也可以是二元运算符,使用规则如下:
  8. (1) 二元运算符
  9. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163051138-743259264.jpg[/img][/align]
  10. (2) 一元运算符
  11. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163050591-1110885269.jpg[/img][/align]
  12. 重要:
  13. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163050178-1744405883.jpg[/img][/align]
  14. [indent]*&data=30;
  15. &data ==>得到变量的地址
  16. *(&data) ==>得到该地址下的值
  17. 变量名本身表示地址下的数值
  18. [/indent][align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163049817-231581331.jpg[/img][/align]
  19. [indent]这里的*和&都是一元运算符,右结合
  20. =是赋值运算符,二元运算符,右结合
  21. 一元运算符的优先级高
  22. [/indent]C语言标准中提到数组名可以用于表示数组的第一个元素的地址,但是此时有两种例外情况。
  23. [size=3]重点[/size]
  24. 第一种情况: 当 [b]数组名 和 &地址运算符 一起使用时,数组名就不表示数组首元素的地址,而表示数组本身[/b]。
  25. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163049270-789792535.jpg[/img][/align]
  26. (&buf+1) 可以知道 &取地址符和数组名一起使用时,数组名不表示数组第一个元素的地址,而[b]表示数组本身的地址[/b],所以[b]+1的动作[/b]是[b]向后偏移整个数组的大小[/b]。
  27. [indent]地址是16进制
  28. 而+1的1是10进制
  29. &buf+1的地址比原先地址大0x14,换成10进制就是20
  30. 20:4*5=20
  31. [/indent][align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163048727-999153363.jpg[/img][/align]
  32. [indent]&是取地址运算符,输出的是地址,32位系统中,存储单元编号32bit等于4字节
  33. 所以只要运算结果是地址的,都是4字节大小
  34. [/indent][indent]第22行*&相互抵消后得到a,a与sizeof单独运算时,数组名a表示数组本身
  35. [/indent]第二种情况:当 [b]数组名 和 sizeof()运算符 单独使用的时候[/b],[b]数组名就不表示数组首元素地址,而表示数组本身[/b]。
  36. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163048219-550347506.jpg[/img][/align]
  37. [size=4]数组初始化[/size]
  38. 思考:用户为数组申请的内存空间是由内核挑选的,那内存地址中是否会存储一些意想不到的值,如果会,那用户如何对数组进行初始化?
  39. 回答:C语言标准中规定数组可以进行初始化,注意:只有定义数组的同时进行赋值才叫初始化!
  40. 格式:[b]数据类型  数组名[数组容量]  = {0}[/b];  比如 int  buf[5] = {0,0,0,0,0};  //数组初始化
  41. 思考:语句 int buf[10] = {0}; 可以把数组的每个元素都设置为0,那 int buf[10] = {1}; 是否表示把数组的每个元素都设置为1?
  42. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163047708-264884278.jpg[/img][/align]
  43. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163047005-2015622405.jpg[/img][/align]
  44. 思考:如果用户在定义数组时还没想好要存储的数据的个数,那数组[]里面是否可以空着不写? 语法上是否符合? 比如  int  buf[]; //用户没有填写数据元素的个数
  45. 回答:语法是符合的,可以在定义数组时不去指定数组的元素个数,但是一般需要在定义数组的同时进行初始化。
  46. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163046367-714056004.jpg[/img][/align]
  47. 思考:如果用户定义数组时并未在[]中说明数组元素个数,但是在定义数组时已经对数组进行初始化,所以系统会自动计算数组所需要占用的内存大小,请问如何计算出数组的有效长度以及如何计算数组元素个数?
  48. 回答:可以利用[b]sizeof()运算符来计算数组的容量[/b],计算出的数组大小是[b]以字节为单位[/b],然后再用[b]数组容量 / 数组中元素的类型[/b]就可以[b]得到数组中元素的个数[/b]。
  49. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163045816-45998959.jpg[/img][/align]
  50. 思考:用户定义了一个数组,并且也对数据正确进行了初始化,但是用户后面准备存储新的元素到数组中,想要把之前存储的元素清空,由于定义数组已经做过初始化的,是否意味着只能把数组中的元素一个一个单独清空?
  51. 回答:不需要,可以调用库函数 [b]bzero()[/b] 以及 [b]memset()[/b] ,可以专门对数组进行处理,尤其是清空数组的处理。[b]memset() 比 bzero() 更灵活[/b]。
  52. 查找:MMU--------内存管理单元
  53. [size=3]重点[/size]
  54. [size=2](1) bzero()[/size]
  55. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163045297-1156872699.jpg[/img][/align]
  56. [indent]void        空
  57. void *        任意类型的指针
  58. [/indent][indent]把'\0'的ASCII码存入
  59. [/indent][align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163044802-303742953.jpg[/img][/align]
  60. [size=2](2) memset()[/size]
  61. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163044416-1972384177.jpg[/img][/align]
  62. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163043901-506228790.jpg[/img][/align]
  63. 思考:用户定义了一个数组,并且也对数据正确进行了初始化,但是用户不小心把超过数组大小的数据赋值给数组,请问编译器是否会报错?以及用户是否可以这么操作?
  64. 回答:[b]编译一般是不会报错[/b],甚至于警告都不会出现,但是在[b]程序运行阶段可能会导致内存错误(段错误)[/b],现在的现象是数组出现越界访问的情况,如果刚好访问的内存是有访问权限的,则运行也不会报错,但是如果访问的内存是没有访问权限的,就会段错误,所以就需要用户设计程序要谨慎细心。
  65. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163043395-2024670952.jpg[/img][/align]
  66. 思考:用户定义一个数组,但是在定义数组之后并没有进行初始化,而是在定义数组之后想要对数组初始化,请问是否可以,如果可以,怎么做?
  67. 不可以
  68. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163042770-1736657319.jpg[/img][/align]
  69. [size=4]字符型数组[/size]
  70. 一般实际开发中,使用数组一般都是为了存储字符序列,C语言中的字符串也属于字符序列,字符串需要使用双引号””进行限制,双引号””表示字符串的首字符的地址,字符串的结束以[b]’\0’作为结束[/b]。
  71. 思考:用户定义了一个字符数组 char buf[5]; 用户想要把一个字符序列abcde这5个字符存储到字符数组中,提供两种方案: [b]char buf[5] = “abcde”;  char  buf[5] ={‘a’,’b’,’c’,’d’,’e’};[/b] 请问两种方案有什么区别?
  72. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163042270-975844373.jpg[/img][/align]
  73. [indent]用字符串的方式赋值时,系统会在末尾自动添加'\0',作为结束标志
  74. [/indent]回答:如果数组的容量刚好和字符串常量中的有效字符的数量一致时,就[b]会导致数组越界[/b],因为[b]字符串常量的末尾有一个转义字符’\0’[/b],也是需要[b]占用1个字节[/b]的存储单元。
  75. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163041528-1819115807.jpg[/img][/align]
  76. [indent]arr+1        代表向后偏移一个元素
  77. &arr+1        代表向后偏移一个数组的元素
  78. [/indent][size=4]数组型数组[/size]
  79. 思考:既然数组中可以存储某个类型的数据,那数组本身也是一个类型,那能否在数组中存储数组呢?如果可以,应该怎么做?
  80. 回答:对于数组型数组而言,就称为[b]多维数组[/b],但是注意:[b]维度是针对用户而言[/b],[b]内存是线性的[/b],是不分行和列的,所以多维数组其实和一维数组的本质是一样的,都是为了申请一块连续的内存空间,并且内存空间存储的数据的类型是一致的,这个只需要把数组作为元素来看待即可。
  81. 注意:不管是几维数组,[b]数组的定义规则: 数组名[元素数量]  + 元素类型  比如 int buf[5][/b]
  82. [indent]除了         数组名[元素数量]        其他的都是元素类型
  83. [/indent]二维数组定义格式 :  元素类型 数组名称[元素数量][元素数量]  比如 int buf[2][3] = {0};
  84. [indent]先找[b]名称[/b]加上后面紧跟的一个[b][][/b],这就是一个数组buf[2]
  85. 剩下的[b]int [3][/b] 就是元素类型
  86. int [3]也是一个数组,没有名字的数组,称为匿名数组
  87. int buf[2][3];
  88. 能容纳2个元素,元素的类型是数组
  89. [/indent]思考:如果定义的是多维数组,那如何去访问多维数组中的某个元素? 应该如何设计程序?
  90. 回答:就可以通过下标的方式或者地址的方式进行访问,下标的方式: int buf[3][4]; 则如果打算访问 buf[1][1] ,就表示访问元素如下图
  91. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163041048-1902060667.jpg[/img][/align]
  92. 通过地址的方式访问: int buf[3][4]; 则如果打算访问 *[i]buf[1][1] ==>  * ( (  [i](buf + 1)  ) + 1 )[/i][/i]
  93. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163040574-336910175.jpg[/img][/align]
  94. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163040071-17675545.jpg[/img][/align]
  95. [indent]3、a[0]        指的是大数组中第一个小数组地址,[b]把a[0]当作小数组的名字[/b],当 sizeof 的操作数是一个[b]数组名[/b]时,它返回的是[b]整个数组的大小[/b]
  96. [list=1]
  97. [*]在表达式 sizeof(a[0]) 中,a[0] 这个 “匿名数组” 的名字,作为 sizeof 的操作数。
  98. [*]sizeof(a[0]) 计算的是类型 int[4] 的大小。
  99. [/list][/indent][indent]20、sizeof(a[3])        sizeof只是计算数据的a[3]的宽度,不进行访问内存的操作,也没有申请那块内存,自然不会有段错误。
  100. [/indent][size=4]柔性型数组[/size]
  101. 思考:用户定义一个数组,但是在定义数组的时候[b]没有想清楚数组的元素数量[/b],所以能否使用一个[b]变量来代替数组元素个数[/b]呢?如果可以,那是否意味着用户可以在运行程序的时候通过[b]键盘对变量赋值,从而实现手动控制数组元素个数[/b]?
  102. 回答:柔性数组在C89标准中是不支持的,是C99标准引入的概念,[b]柔性数组也被称为变长数组[/b],但是注意:当[b]数组的内存一旦确定,则不会因为变量发生变化导致数组长度变化![/b]
  103. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163039453-421473424.jpg[/img][/align]
  104. [size=4]匿名型数组[/size]
  105. C99标准中支持匿名数组,但是匿名数组一般都是在函数参数中或者在多维数组中使用,很少单独使用。
  106. 比如二维数组  int buf[3][4] ;  --->  buf[3] 数组的每个元素的类型是 int [4] ,就是匿名数组。
  107. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163038724-1657471906.jpg[/img][/align]
  108. [size=4]零长度数组[/size]
  109. [b]GNU组织[/b]在C99标准的柔性数组的基础之上拓展了一个新的概念,叫做零长数组,也就是[b]数组的长度可以是0[/b],但是由于[b]数组长度是0,所以操作系统是不会提供内存单元给数组的[/b]!
  110. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163038151-660799393.jpg[/img][/align]
  111. 注意:零长度数组是[b]不会得到内存,但是是可以访问[/b]的,一般都是结合[b]C语言的结构体一起使用[/b],可以用于对结构体进行拓展,所以[b]零长度数组也属于柔性数组的一种[/b]。
  112. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163037419-604640523.jpg[/img][/align]
  113. [size=5]C语言的指针[/size]
  114. [size=3]指针的概念[/size]
  115. [b]程序是需要载入内存[/b]中运行,内存是有范围的,对于32位系统,内存地址范围是0x0000_0000~0xFFFF_FFFF,也就是[b]内存大小为4GB[/b],内存地址指的是内存中单元的编号,编号是固定的。
  116. 所以内存地址(存储单元的编号)本质就是一个整数,对于32位系统而言,地址所对应的编号是[b]4字节的正整数[/b]。
  117. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163036898-67435604.jpg[/img][/align]
  118. 用户想要访问内存地址下面的数据是比较麻烦的,因为地址比较难记忆,内存地址都是由内核管理,所以C语言规定用户有权利对内存地址进行命名,比如变量名字、数组名字.........,所以标识符就可以和操作系统提供的内存单元建立映射关系。
  119. 思考:既然用户可以定义变量来存储数据,那能否把内存地址当成数据存储在一个变量中?
  120. 回答:是可以的,因为存储单元的地址本质就是一个整数,如果是在[b]32bit系统[/b]下,则只需要[b]4个字节[/b]的存储单元就可以完成存储。
  121. 思考:既然内存地址可以当做数据存储在一个变量中,那内核[b]如何区分变量中的数据是作为普通数据还是作为内存地址呢[/b]?
  122. 回答:[b]操作系统不需要区分[/b],但是作为用户而言,需要区分该变量下存储的是地址还是普通整数,所以C语言表中规定:用户如果打算定义一个变量来存储一个内存地址,则需要[b]定义变量的时候指明该变量中存储的是一个地址[/b]。
  123. [size=3]指针的定义[/size]
  124. C语言中把用于[b]存储地址的变量[/b]称为[b]指针变量[/b],因为[b]通过变量中的地址可以指向某个存储单元[/b]!  指针指向的是地址,所以可以把指针理解为地址,也可以把地址当做指针使用,注意:如果*[i]打算获取某个地址下的值,必须使用 * 间接运算符 , [i]地址 == 地址下的值[/i][/i]
  125. *[i]指针变量定义格式:数据类型  [i]变量名;[/i][/i]  比如 int  *p;  or  char  *p;  or  int * buf[5];
  126. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163036570-834121099.jpg[/img][/align]
  127. [indent]存储的是地址,地址是4字节内存空间
  128. int *p2;        向存储器申请[b]4字节[/b]的内存大小,用来存储地址,地址下存储的数据类型是int型
  129. 4字节的原因,是由于操作系统是32位的,32操作系统的存储单元的地址长度都是4字节
  130. [/indent]思考:既然指针变量可以存储一个内存地址,那请问[b]内核是否会为指针变量分配内存空间[/b]?
  131. 回答:当然会分配,因为定义变量的目的就是为了申请内存单元,32bit系统下需要4个存储单元才能记录一个地址,而记录的地址和变量本身的地址是不一样的。[b]变量的存储单元就相当于是一个容器,记录的地址就相当于数据而已[/b]。
  132. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163035920-915238591.jpg[/img][/align]
  133. 思考:用户定义了一个指针变量,但是此时并没有打算让该指针变量指向某个内存地址,请问内核分配给指针变量的[b]内存空间中是否会存储一些未知的数据[/b]?如果存在,应该如何解决?
  134. 回答:是会的,所以为了提高程序的可靠性,为了避免异常出现所以就算不存储有效地址,也应该对定义的指针变量进行初始化,注意:[b]对指针变量进行初始化,则应该把指针变量对应的内存初始化为0[/b],但是[b]0只是一个整数,并不是地址,而指针变量就是应该存储地址[/b]。
  135. 用户应该把*[i]普通整数0进行强制转换,转换为一个地址 0x00000000 --> (void  [i])0x00000000[/i][/i]
  136. [indent]void  *任意格式
  137. [/indent]int  *p = (void *)0x00000000; //可读性较差,所以C语言中提供了一个宏定义 [b]NULL[/b] 空指针
  138. [size=6]define NULL        ((void *)0)[/size]
  139. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163035394-748875532.jpg[/img][/align]
  140. 可以看到,linux系统的内存中有一部分内存是属于[b]保留区[/b],保留区地址范围就是[b]0x0000_0000 ~ 0x0804_8000[/b],属于[b]用户没有权限访问的内存空间[/b],一旦用户访问这块区域,就会[b]导致段错误[/b]。
  141. [b]int  *p = NULL[/b];  //对指针变量进行初始化,[b]目的是防止野指针出现,为了避免段错误的[/b]!!!
  142. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163034906-1856217101.jpg[/img][/align]
  143. 思考:已经知道内存中有一块保留的空间,程序是没有权限访问的,但是用户能否定义一个指针变量指向这块空间?
  144. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163034537-43409579.jpg[/img][/align]
  145. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163034179-1390968579.jpg[/img][/align]
  146. [indent]堆内存属于匿名内存,只能通过内存间接堆访问
  147. [/indent]回答:是可以的,[b]但是只能用指针变量记录该地址,但是不能通过指针变量间接访问该地址,如果间接访问,则会导致内存异常,发生段错误[/b]。
  148. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163033815-665390338.jpg[/img][/align]
  149. 思考:既然[b]指针变量中存储的是一个内存地址[/b],[b]内存地址的本质就是一个整数[/b],所以能否对整数进行算术运算呢?
  150. 回答:是可以的,只不过普通整数的算术运算和地址的算术运算的理解是不同的,一般对于普通整数可以进行算术运算,则结果也是一个整数。
  151. 但是对于指针变量中存储的地址进行算术运算,一般[b]只能进行加法运算和减法运算[/b],对地址进行加法运算和减法运算,[b]其实就是对地址进行偏移而已[/b],偏移的单位一般是应该以[b]字节为单位[/b]。
  152. 注意:对于指针变量的偏移,要[b]考虑到变量中存储的地址的数据类型[/b],所以 [b]地址 + 1 不表示存储单元向后偏移1个字节,应该是向后偏移 (1 * 数据类型)个字节[/b]。
  153. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163033401-1346380891.jpg[/img][/align]
  154. [size=3]数组指针[/size]
  155. 思考:既然可以用指针变量指向另一个变量的地址,请问能否用[b]指针变量指向数组的地址[/b]?
  156. 回答:当然可以,就相当利用[b]指针变量来对数组的地址进行备份[/b],[b]提高了访问数组的安全性[/b],而[b]利用指针变量来指向数组的地址,被称为数组指针[/b]!!!!   int buf[5];  int  *p = buf;
  157. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163033027-1008695483.jpg[/img][/align]
  158. [size=2]常用的优先级[/size]
  159. [b]运算符的优先级[/b]:()>[]>*
  160. [indent]考虑优先级,得先让变量名和*先结合在一起在可以,这样才是定义的指针,
  161. 然后再写定义数据类型,int [10],
  162. int *a[10],这个不对,这里是[]先与a结合成为数组a[10],数据类型位int *,
  163. 其含义为,一个有10个指针的数组
  164. [/indent][indent]要让*与a先结合,就用()
  165. [b]int (*a)[10][/b],这个才是这个题的答案
  166. [/indent][size=2]数组指针的访问[/size]
  167. 如果此时用户需要[b]访问数组中的元素[/b],可以通过[b]数组下标[/b] and [b]指针访问[/b],两者的区别如下:
  168. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163032576-1451081656.jpg[/img][/align]
  169. 笔试题:请分析以下程序,根据自己对程序的理解回答出程序的运行效果,不许讨论和抄袭!
  170. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163032176-1066472806.jpg[/img][/align]
  171. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163031799-590897937.jpg[/img][/align]
  172. 笔试题:请分析以下程序,根据自己对程序的理解回答出程序的运行效果,不许讨论和抄袭!
  173. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163031324-63689321.jpg[/img][/align]
  174. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163030813-1118586977.jpg[/img][/align]
  175. 思考:用户打算定义一个二维数组,并且用一个指针变量来存储数组的地址,现在想用[b]指针变量来访问二维数组中的元素[/b],请问如何访问?
  176. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163030385-1334431869.jpg[/img][/align]
  177. [align=center][img]https://img2024.cnblogs.com/blog/3723495/202512/3723495-20251224163029804-620474443.jpg[/img][/align]
  178. [code]int **(*p)[2][3];(*p)该变量是指针,存储一个地址-->[2]代表存储的地址下的数据是一个数组,可以容纳两个元素    [3]代表数组中的元素类型是匿名数组,匿名数组内有3个元素-->*表示匿名数组中存储的元素类型是一个指针,存储一个地址-->*表示存储的地址下的数据还是指针,存储一个地址-->int 指的是指针存储的地址下的数据是int型
复制代码
指针数组

思考:既然数组可以存储同一类型的数据,请问能否在一个数组中存储指针变量的地址???
回答:是可以的,如果在一个数组中,每个元素都是一个指针,则C语言中把这种结构称为指针数组
指针数组的定义格式: 数据类型  *数组名[元素个数];     // 比如  int  *buf[5];
注意:*[]后缀运算符 优先级高于 间接运算符 ,所以 buf[5] 作为一个整体,剩余部分就是数组中元素的类型,所以 int  *就是数组中元素的类型,其中int是用于修饰指针指向的地址中数据的类型。


二级指针

思考:既然可以使用一个指针变量来存储另一个变量的地址,能否定义一个指针变量,然后来存储另一个指针变量的地址?如果可以,那如何可以访问到最终内存地址下的数据?
回答:是可以的,如果一个指针变量中存储的地址是另一个指针变量的地址,则把这种结构称为二级指针。
二级指针定义格式: int  data; //整型变量  int  *p1 = &data; //指针变量  int **p2 = &p1;


习题

练习:用户现在定义一个int buf[5] = {1,2,3,4,5}; 现在用户定义一个数组指针来存储数组的地址, int  *p = buf;
请问 printf(“%d\n”,*p++);
printf(“%d\n”,(*p)++);  请问两句话的输出结果?
单目运算符,右结合性,++与*的优先级相同,但是++是后置自增
所以*p++先进行 *p得到buf的地址,再进行++自增
特性是 “先使用原值,再移动指针”。

  • 先解引用 p,获取 buf[0] 的值 1,作为输出结果;
  • 指针 p 向后移动一位,指向 buf[1](值为 2)。


  • 因此第一句输出:1。

字符串常量,本身表示字符串第一个字符的地址
%s是字符序列,碰到'\0'才会停止
重要:



出错:

没看仔细

c注意()优先级




出错:

*p指针,存储的数据类型是char
p=s[1]="two"中的't'的地址
p+1:由于p的类型是char,p+1得到'w'的地址
出错:

0x67a9 是个整型,想要当成地址,得使用强转换 int *或者unsigned int *
再使用间接运算符,进行赋值






到不了10,
3p对的,可以交换,只要E2确保是整数就能交换




C语言的函数

耦合、内聚
C语言程序的基本单位是函数,C语言是面向过程的一门编程语言,采用“自顶向下”的设计思想,采用的方案是把一个大问题拆解为很多个小问题,每个小问题单独进行解决,每个小问题可能需要多条语句才能解决,为了提高效率,所以就把可以解决问题的多条语句构成一个块语句,C语言中把这种块语句就称为函数。
C语言标准在发布的同时也随之发布了标准C库,标准C库中提供了已经封装好的函数接口,目的也是方便用户提高开发效率,不过预先封装好的函数属于*库函数*。库函数根据发布者的不同,又分为*标准库**第三方库*,比如标准C库。
函数的本质就是一段可以重复使用的代码块,用户不想每次都复制这段代码,就可以把这段可以重复使用的代码封装为一个函数接口。

函数定义

思考:既然函数可以很大程度提高开发效率,应该如何去定义一个函数呢?有没有注意事项?

//函数有参数列表,则应该在函数名称的()中写清楚每个参数的类型,以及每个参数的名称
函数类型  函数名称(参数1类型 参数1名称,参数2类型 参数2名称.........)
{
}
//函数的参数是可以可选的,如果没有参数,则需要在函数名称的()中填写void即可
函数类型  函数名称(void)
{
}
注意:void在C语言标准中是一个关键字,含义具有空的意思,所以如果在参数列表中出现,则表示函数没有参数,同样,如果void是函数类型,则表示函数没有返回值
注意:函数的类型其实指的是函数的返回值的类型,C语言标准中规定函数类型可以是void或者是对象类型(基本数据类型 int char long float...  + 复杂数据类型 struct union.... +指针)
但是函数的返回值类型不允许是数组!!!!
注意:如果函数有返回值类型,则函数内部的需要返回的数据的类型必须要和函数的返回值类型一致,则需要在函数内部调用return语句实现
int 函数名称(void)
{
return 3.14;  //不允许,因为实际返回的数据的类型和定义函数的时候声明类型不一致
}
void 函数名称(void)
{
return 10; //不允许,因为void作为函数的类型,表示函数是没有返回值的!!!!!!!!!
}
int [10] 函数名称(void)
{
int buf[10] = {1,2,3,4,5};
return buf;  //不允许,因为函数的返回值类型不允许是数组,但是可以选择传递地址
}
**int *** 函数名称(void)
{
int buf[10] = {1,2,3,4,5};
return buf;  //允许的,因为函数的返回值类型不允许是数组,但是可以选择传递地址!
}
注意:如果函数的类型是一个指针类型,则表示该函数可以返回一个地址,就把这种函数称为指针函数

耦合内聚

思考:既然C语言程序的基本单位是函数,能否在一个已经存在的函数中定义一个新的函数?
回答:不可以!C语言中函数都是独立的个体,不允许在一个函数内部定义新的函数,但是允许在一个函数内部调用其他的函数!设计函数应该做到*低耦合,高内聚*
耦合:两个或者两个以上的模块之间关联的紧密程度叫做耦合,耦合度越低越好
内聚:一个模块内部的各部分的紧密程度,内聚度越高越好

函数调用

思考:如果用户打算封装一个函数实现某个功能,但是此时用户还没想好函数对应的块语句怎么写,只是把函数的名称和返回值类型以及参数列表写了出来,那能否在一个函数中进行调用?
回答:是可以调用的,但是遵循一个“先定义,后使用”原则,由于C语言中程序都是以函数为单位,并且程序的入口是主函数main(),所以应该把用户自定义的函数定义在main()函数之前,然后在ma in()函数中进行调用。

但是,有时用户可以在程序设计时是先在main()中调用了某个自定义函数,然后在main()函数后面定义了子函数,此时编译会报错,会提示:子函数未定义,为了避免此类问题,C语言中也是支持“先声明,后定义”。

练习:用户打算设计一个函数,把两个整数传递到函数内部,从而实现计算两个数中较大的数并把较大的整数作为结果输出到终端,请问如何设计程序?

函数参数

思考:既然一个函数可以对数据进行处理,请问如何把要处理的数据传递给函数?应该如何操作?
回答:需要在设计函数的时候说清楚函数需要传递的参数的类型以及参数名称,都是在定义函数的时候通过函数的参数列表传递。
函数的参数列表是在后缀运算符()里面进行填写,()中的参数只是一个函数的助记符, 只是为了描述需要传递给函数的参数,所以函数的参数一般称为形式参数,简称为形参
*定义函数*的时候函数参数列表中的形参是不占内存的,只是为了提醒用户参数的数量和类型!
用户在调用函数接口时,需要按照函数的参数列表来向函数提供对应的数据,数据的数量和数据的类型必须和形参一致
注意:当一个函数被调用之后,函数的形参才会得到对应的内存,并且函数的形参的内存只会在函数内部生效,当函数调用完成后,则函数形参的内存会被系统自动释放
注意:当用户调用一个函数时,如果函数有参数列表,则用户需要提供对应的数据给函数,而用户提供的数据的类型必须和函数参数类型一致,用户实际提供的数据被称为实际参数,简称为实参,而实参是必须存在的,实参的形式可以是表达式、常量、变量、地址........
  1. void func(int a,int b);int main(int argc,char const *argv[]){    int c=10;    int d=20;    //10和20是变量,有存储单元    func(c,d);        //10和20是常量,有存储单元    func(10,20);}//函数的形参是调用函数时才会得到内存,在函数结束时会被释放//传参时,是把实参的值传递
复制代码
单向传递


单向传递:只是把实参的值传递给函数作为参数,在函数内部对数值进行修改是不会影响外部实参的
思考:一个函数的参数列表中的参数有对应的类型和名称,那另一个函数在调用该函数的时候,传递给函数的需要处理的数据(实参)的类型和名称是否需要和参数列表(形参)完全一致?
回答:实参的名称和函数形参的名称不需要一致,只需要确保实参的类型和函数形参的类型一致即可,如果类型不一致,则会出现数据精度异常

思考:既然函数数据传递的过程是单向的,请问用户如何获取函数内部对数据的处理结果呢?
回答:可以通过函数的返回值获取函数的处理结果,函数中调用return语句可以把结果返回给被调用的位置
当func()函数的返回结果sum的值给到主函数时,函数的sum释放内存
双向传递

如果不打算调用return语句,则可以选择把实参的地址作为参数传递给函数内部,这样函数内部对地址中的数据进行修改,则函数外部的实参地址下的值也会变化,只不过此时函数参数类型应该是指针才可以。
  1. #include int *xxx(void)  // 函数返回int类型指针{    int buf[5] = {1, 2, 3, 4, 5};    printf("%d\n", buf[0]);  // 输出数组第一个元素1    return buf;  // 注意:此处返回局部数组地址,存在风险}int main(int argc, char const *argv[]){    int *p = xxx();    *(p + 0) = 10;  // 尝试修改指针指向的内存(实际为无效操作)    printf("%d\n", p[0]);  // 输出结果不确定(未定义行为)    return 0;}存在段错误的风险    因为p存储的是局部变量的地址,但是由于函数使用完就会释放,p只能存储这个地址,但是不能进行访问p存储的内容
复制代码
生命周期

思考:程序中全局变量和局部变量在使用的时候是否有区分?有哪些使用细节需要注意??
回答:对于生命周期是指变量的生命周期,也就是变量从得到内存到释放内存的时间就是变量的生命周期,程序中变量如果按照存储单元的属性分类,可以分为变量和常量,也可以按照生命周期进行划分,可以分为全局变量和局部变量
(1) 局部变量:在函数内部定义的变量或者在某个复合语句中定义的变量都称为局部变量!
  1. {    {        int a;    }}a的作用域是第二个{}a的生命周期是第一个{}
复制代码
(2) 全局变量:在所有的函数外部(在所有复合语句外部)定义的变量就被称为全局变量!

作用范围

作用范围指的是定义的变量的作用域,也就是变量的有效使用范围,对于全局变量而言,作用域是针对整个程序,所以程序中任何一个函数都有访问权限。对于局部变量而言。作用域只针对当前局部变量的复合语句有效。

注意:当全局变量的名称和局部变量名称相同时,则应该遵循*“就近原则”*,也就是应该优先使用同一个作用域内的变量,如果该作用域中没有该变量,则可以扩大作用域
数组传递

思考:通过学习已经知道可以把变量的值或者地址当做参数传递给函数进行处理,但是有时用户需要连续处理多个相同类型的数据,但是不想定义多个形参,请问如何解决该问题???
回答:可以选择把多个类型相同的数据构造为一个数组,然后把数组作为参数传递给函数,本质就是把数组的地址传递过去,此时分为两种方案:


思考:如果把数组的地址当做参数传递给函数,那用户如何知道实参数组的长度是多少???
回答:如果打算把数组作为参数传递给函数,则应该连同数组的长度一同作为参数传递给函数,而数组长度应该使用sizeof进行计算。

思考:既然可以把一维数组的地址作为参数传递,请问能否把多维数组传递给函数处理???
回答:一维数组和多维数组其实没有区别,因为都是把数组的首地址传递过去,只不过在函数内部访问数组元
  1. void func2(char buf[2][5],int size);void func2(char **buf,int size);void func2(char *buf[2],int size);
复制代码

思考:如果用户打算在一个函数中定义一个数组用来存储已经处理好的数据,但是C语言中规定不允许返回一个数组类型,当函数调用完成后函数内部的内存会被内核释放掉,也就意味着处理好的数据都会丢失,请问应该如何处理?
回答:由于C语言不支持函数的返回值类型是一个数组,但是可以选择把数组的地址作为返回值,此时函数的返回值类型就应该是指针才对。

思考:程序中数组的类型和函数的返回值类型是一致的,为什么编译程序会报警告,运行时也出错,是什么原因导致的?应该怎么解决?


出现段错误的原因:因为子函数中的变量buf的生命周期是在函数内部的,所以当函数调用完成后,则数组buf的内存会被系统自动释放,此时数组buf的地址对应的存储单元就没有访问权限了
虽然得到了数组buf的地址,但是由于用户没有该地址的访问权限,所以访问时会出现段错误。
(1) 解决方案:可以选择把函数内部的数组定义为全局变量,此时程序中任意函数都可以访问,并且数组内存是在程序终止后才会被释放
(2) 解决方案:可以选择把函数内部的局部变量的生命周期延长,此时需要使用C语言中的存储类修饰符,就是C语言关键字之一的static关键字,static具有静态的含义,可以把局部变量的生命周期进行延长。
static

内存角度分析:如果在函数内部定义一个局部变量,则系统会从内存分区中的*栈空间*中分配一块内存给局部变量,栈空间是由系统自动管理,所以当函数调用结束时系统会自动释放该局部变量的内存。
如果函数中定义的局部变量使用static关键字进行修饰,则系统会从*全局数据区*分配内存空间给该局部变量,全局数据区的生命周期是跟随程序的,不会因为函数结束而释放。

static除了可以修饰局部变量外,也可以用于修饰函数,如果一个函数在定义的时候使用static关键字进行修饰,则可以限制函数的作用域为文件内部有效
同一个函数内,静态局部变量不会重复定义
[code]void func(void){    static int b=0;    b++;    printf("%d\n",b);}int main(int argc,char *argv[]){    int a=1;    for(a=1;a

相关推荐

2026-1-17 23:48:08

举报

2026-1-19 02:11:04

举报

懂技术并乐意极积无私分享的人越来越少。珍惜
您需要登录后才可以回帖 登录 | 立即注册