D1 必备Linux命令和C语言基础
环境搭建
虚拟机安装
VMware-workstation 10
linux系统安装
ubuntu-14.04.5-desktop-i386.iso
常规配置
注意分区不同于Win 系统
文件和目录相关命令
-
Linux 文件系统是一个分层的树形结构
-
目录结构及目录路径
-
文件系统层次结构标准FHS
Filesystem Hierarchy Standard(文件系统层次结构标准)
Linux是开源的软件,各Linux发行机构都可以按照自己的需求对文件系统进行裁剪,所以众多的Linux发行版本的目录结构也不尽相同。
为了规范文件目录命名和存放标准,Linux基金会颁发了FHS(Filesystem Hierarchy Standard)。
/bin | 是二进制(binary)英文缩写,放命令 |
---|---|
/boot | 存放的都是系统启动时要用到的程序 |
/dev | 包含了所有Linux系统中使用的外部设备 |
/etc | 存放了系统管理时要用到的各种配置文件和 |
/lib | 存放系统动态联接共享库的 |
/home | 普通用户的主目录 |
/root | 根用户(超级用户)的主目录 |
-
浏览目录
当打开命令终端窗口,用户就处在自己的用户主目录的位置。
学习文件系统命令,就从自我定位开始。 -
ls 列目录内容
Usage:
ls [options] [files_or_dirs]
Example:
ls -a
ls -l 显示权限位
ls -R 递归查看当前目录下文件夹及文件df -T 显示文件目录
-
文件的权限
-
cd 改变目录
回到家目录
cd
回到上一级目录
cd …
回到上一次的工作目录
cd - -
cat -s 文件名 不显示空行
-
cat -b 文件名 显示行号
-
nl 文件名 显示行号查看文件
-
teal 默认尾10行
-
head 默认头10行
-
cp 复制
Usage:
cp [options] file destination
常用选项
i覆盖时交互提示
r对文件夹递归
复制多个文件到文件夹
cp [options] file1 file2 dest -
mv 移动
Usage:
mv [options] file destination
移动多个文件:
mv [options] file1 file2 destination
更改文件名 -
创建文件 touch
-
创建文件夹
mkdir -p 递归创建 -
删除文件夹 rm -r 文件夹
vi编辑器的使用
- 取消vi 编辑器当前行下划线 找到 /home/~/.vimrc 将其中set cursorline删掉或注释掉 ,用 “ " ” 注释
- i 当前光标位置开始编辑
- I 当前光标位置所在行行首开始编辑
- a 当前光标位置之后开始编辑
- A 当前光标位置所在行尾开始编辑
- o 当前光标位置所在行之后新起一行开始编辑
- O当前光标位置所在行之前新起一行开始编辑
- :! Command 在vi中执行外部命令Command,按回车键可以返回vi继续工作
- :q 退出没有修改的文件
- [N]x 删除从光标位置开始的连续N个字符(并复制到编辑缓冲区)
- (命令行模式)yy 复制当前行
- (命令行模式)[N]yy 复制当前行至下方N-1行
- (命令行模式)dd 剪切当前行
- (命令行模式)[N]dd 剪切当前行至下方N-1行
- p 粘贴
- P 当前行上一行粘贴
- u 撤销
- ctrl + r 恢复撤销
- x 保存并退出
- w fileName 备份
- r fileName 引入文件,(Read)读入File指定的文件内容插入到光标位置
- 左右 上下 hlkj
- (命令行模式)1G 到第一行 or gg
- (命令行模式)G 到最后一行 or GG
- :行号,到指定行
-
/str ,查找指定字符串,n下一个匹配 N 上一个匹配
- : s /oldstr/newstr/g 省略范围是当前行替换(/g 全部替换,不加默认只换每行匹配的第一个)
- :.,$ s/oldstr/newstr/g 全部替换(/g 全部替换,不加默认只换每行匹配的第一个)
- :1,$ s/oldstr/newstr/g 全部替换(/g 全部替换,不加默认只换每行匹配的第一个)
- :% s/oldstr/newstr/g 全部替换(/g 全部替换,不加默认只换每行匹配的第一个)
- nyb 复制光标所在的前n个单词
- nyw 复制光标所在的后n个单词
- y0-将光标至行首的字符拷入剪贴板
- y$-将光标至行尾的字符拷入剪贴板
- d0-将光标至行首的字符剪切到剪贴板
- d$-将光标至行尾的字符剪切到剪贴板
- range y-块复制 range d-块剪切
基础知识
-
冯.诺伊曼模型
-
存储器的分类
主存储器即内存。程序中待处理的数据和处理的结果都存储在内存中。
外存储器是用来长期保存数据的大容量存储器。
寄存器是CPU内部的高速存储器,速度快,数目少 -
什么是程序
广义上讲,为了实现一个特定的目标而预先设计的一组可操作的工作步骤,称之为一个程序。
程序就是系统可以识别的一组有序的指令。存储在磁盘上,被加载到内存中执行。 -
c 语言产生于20世纪70年代美国贝尔实验室。D.M.Ritchie 设计,以B语言为基础。
-
34 种运算符,效率仅比汇编低%10-20%
-
机器语言 ——>汇编语言——>面向过程高级语言——>面向对象高级语言
-
程序:编辑——>编译——>链接——>运行
-
GCC 基本语法 gcc [option | filename]
-
GCC 编译流程
- 预处理 (主要是对源程序中伪指令和特殊符号的处理) 选项-E 可以使编译器在预处理结束时就停止编译 ,目标文件后缀名.i,举例:gcc -E -o hello.i hello.c
- 编译 (主要对预处理后的输出文件进行词法分析和语法分析,找出不符合语法规则的部分) 选项-S 可以使编译器在编译结束时就停止编译 ,目标文件后缀名.s,举例:gcc -S -o hello.s hello.i
- 汇编 (把汇编语言代码翻译成机器语言代码的过程)选项-c 可以使编译器在汇编结束时就停止编译 ,目标文件后缀名.o,举例:gcc -c hello.s -o hello.o
- 链接 (在C语言中,源程序(.c文件)经过编译程序编译之后,会生成一个后缀为“.OBJ”的二进制文件(称为目标文件);最后还要由称为“连接程序”(Link)的软件,把此“.OBJ”文件与c语言提供的各种库函数连接在一起,生成一个后缀“.EXE”的可执行文件。)举例:gcc hello.o -o hello 目标文件hello
数据的表示
- 数值型数据和非数值型数据
送入计算机的数字,字母,符号等信息必须转换成0、 1组合的数据形式才能被计算机识别。
能够进行算术运算得到明确数值概念的信息成为计算机数值数据,其余的信息成为非数值数据。 - 数值数据的表示
包括十进制、二进制、十六进制和八进制。
基数与各数位的权
基数是指该进位制中允许选用的基本数码的个数。如十进制数,基数为10, 可选用0、1、2……9共10个不同数码中的任何一个。
而位权的大小是以基数为底,数字所在位置的序号为指数的整数次幂。
123 = 1X100 + 2X10 + 3X1
= 1X102 + 2X101 + 3X100 - 一个十六进制可以转换成四个二进制数表示
- 二进制
正数:原码=反码=补码
负数:
原码 符号位为1
反码 按位取反
补码 反码加1 - 非数据值数据:文字、符号、图像和逻辑信息
非数值数据包括文字、符号、图像、语言和逻辑信息等,也都是以0、1形式存在。
字符数据在机器内也被变换成二进制编码的形式。国际上普遍采用的一种编码是美国国家信息交换标准代码,简称为ASCII码。
char unsigned char 1字节 00000000~11111111 0~255 - ASCII 码表
第一部分由 00H 到 1FH 共 32 个,一般用来通讯或作为控制之用,有些字符可显示于屏幕,有些则无法显示在屏幕上。
第二部分是由 20H 到 7FH 共 96 个,这 96 个字符是用来表示阿拉伯数字、英文字母大小写和底线、括号等符号,都可以显示在屏幕上。
第三部分由 80H 到 0FFH 共 128 个字符,一般称为『扩充字符』,这 128 个扩充字符是由 IBM 制定的,并非标准的 ASCII 码。这些字符是用来表示框线、音标和其它欧洲非英语系的字母。
程序的编译和调试
- gcc -o hello hello.c -o输出可执行文件,-c只要求编译器输出目标代码,-g 用于调试 ./hello 查看输出结果
- ctrl + shift + n 再开一个当前路径窗口
- chtl + shift + t 再开一个当前路径标签
- chtl + alt + t 开一个新用户目录窗口
- printf(“%s,%s,%d\n”,FILE,FUNCTION,LINE);
- 设置删除断点
- 调试运行环境相关命令
-
数据相关命令
-
堆栈相关命令
D2 数据类型、常量、变量及运算符
数据类型
-
类型
逻辑类型。只有两个量true和false,表示逻辑真值和逻辑假值。
整数类型。包括char, short, int和long。
浮点类型。包括float和double。
void类型。主要用于说明不返回值的函数或指向任一类型的指针等。 -
八进制整数以0开头,十六进制整数以0x开口,长整型以L或l结尾,无符号整型以U或u结尾。
-
布尔类型非零为真
布尔类型使用需要引入头文件<stdbool.h>
编译,查看宏展开example:gcc -E demo.c -o demo.i
原始bool类型为_Bool 有0,1 两个值,非零为真 -
char -128~127 -127 补码 10000001 -128 补码10000000
-
short -32768~32767 or 0-65535
-
数据类型取值范围参考文件:/usr/include/limits.h
常量
- 两个单引号引起来
- 可以看作是一个字节的正整数
- 常见A=65;a=97;空格=32; ‘0’=48
- 字符串常量以 \0 结尾,ex:“9”=‘9’+‘\0’
- 标识常量 #define 定义一个宏名字
- 注意宏展开是原样展开,如有特殊运算需加括号。
变量
变量声明和变量定义
变量定义:
用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
变量声明:
用于向程序表明变量的类型和名字。定义也是声明,extern声明不是定义
- 由字母、数字、下划线构成。不能以数字开头,不能与关键字重名。
- 变量的说明
变量在程序中使用时,必须预先说明它们的存储类型和数据类型。
变量说明的一般形式是:
<存储类型> <数据类型 > <变量名> ;
<存储类型>是关键词auto、register、static和extern
<数据类型>可以是基本数据类型,也可以是自定义的数据类型
static关键字有什么作用?
1.当static修时局部变量时,变量只能被初始化一次,且static静态局部变量,其生命周期为整个进程,直到程序结束。(static修时变量时,默认值为0)
2.当static修时全局变量时,只在当前源文件有效,不能在其他源文件使用
3.当static修时函数时,只在当前源文件有效,不能在其他源文件使用
-
作用域:程序中可以访问一个指示符的一个或多个区域,即变量出现的有效区域,决定了程序的哪些部分通过变量名来访问。
-
函数原型作用域 : 函数原型中的参数,始于"(" 终于 “)” .
-
局部变量:在函数内部定义,仅存在于定义该变量的执行代码块中,进入模块(压入堆栈)时生成,退出模块(弹出堆栈)时消亡。
-
全局变量:作用域为源文件,可被源文件中的任何一个函数使用。
全局变量定义:[extern] 类型说明符 变量名,变量名 ...... 全局变量声明(出现在使用该变量的各个函数内,不能再赋初值,只是表明再函数内要使用某外部变量):extern 类型说明符 变量名,变量名......
-
-
存储模型[生命周期、从作用域、初始值等考虑]
变量是程序中数据的存储空间的抽象。分为静态存储和动态存储两种。
静态存储变量:程序编译时分配一定空间,并一直保持不变,直至程序结束。如全局变量。
动态存储变量:程序执行过程中使用它时才分配存储单元,使用完毕立即释放。如函数形参。时而存在时而消失。
变量的生存期:变量存在的时间。-
自动存储类变量,auto修饰 ,声明在函数内。[具有动态存储期、代码块的作用域和空链接,没有初始化则值随机]
C语言中的变量分为外部链接、内部链接、空连接三种: (1)外部链接:外部链接的变量可以在多个文件中使用; (2)内部链接:内部链接的变量只能在一个文件中使用;文件作用域变量前面加关键字static就是内部链接,表明该变量为定义该变量的文件私有。 (3)空链接:由定义变量的代码块作用域所私有;
-
寄存器、register 修饰,与自动存储类类似,寄存器变量,位宽有限,数量有限。变量放到寄存器就没有地址的概念了,地址是针对内存讲的。合理使用可提高性能,滥用可能降低性能。[具有动态存储期、代码块的作用域和空链接,没有初始化则值随机]
-
静态存储类型
3.1 静态、 空链接存储类型,static 修饰,既可以在函数体内,也可以在函数体外说明。int类型默认值为0,程序不结束,值不消失。[具有静态存储期、代码块的作用域,未初始化时字节设定为0]
3.2 静态、外部链接,未使用static 修饰的全局变量
,[具有静态存储期,文件作用域,未初始化时字节设定为0],在使用外部变量的函数中使用extern 关键字来再次声明。在其他文件中定义的,则必须使用extern。
3.3 静态、内部链接,使用static 修饰的全局变量
,其他文件中无法使用。[具有静态存储期,定义该变量的源文件内有效,未初始化时字节设定为0]
-
-
:vsp filename 在底行模式下编辑另外一个文件
-
什么是void*类型的指针?
void *指针就是指向任何类型的指针,在编译的时候不会确定其指向的类型,是在程序中进行指向的。
结构体和共用体
结构体
结构体解决的是什么问题?
结构体是用户自定义的新数据类型,在结构体中可以包含若干个不同数据类型和不同意义的数据项(当然也可以相同),从而使这些数据项组合起来反映某一个信息。
例如,可以定义一个职工worker结构体,在这个结构体中包括职工编号、姓名、性别、年龄、工资、家庭住址、联系电话。这样就可以用一个结构体数据类型的变量来存放某个职工的所有相关信息。并且,用户自定义的数据类型worker也可以与int、double等基本数据类型一样,用来作为定义其他变量的数据类型
形式
struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;
:
数据类型 成员名n;
};
在大括号中的内容也称为“成员列表”或“域表”。
其中,每个成员名的命名规则与变量名相同;
数据类型可以是基本变量类型和数组类型,或者是一个结构体类型;
用分号“;”作为结束符。整个结构的定义也用分号作为结束符
Example:
定义一个职工worker结构体如下:
struct worker
{
long number;
char name[20];
char sex;
int age; // age是成员名
float salary;
char address[80];
}; //注意分号不能省略
int age = 10; //age是变量名
说明:
结构体类型中的成员名可以与程序中的变量名相同,二者并不代表同一对象,编译程序可以自动对它们进行区分。
最后,总结一下结构体类型的特点:
(1)结构体类型是用户自行构造的。
(2)它由若干不同的基本数据类型的数据构成。
(3)它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有用它定义变量时才分配空间。
对于结构体变量中的字符数组如果不是赋初值需要借助strcpy()方法。
结构体类型变量在存储时总是按各成员的数据长度之和占用内存空间,但是要注意字节对齐。
C语言结构体字节对齐规则
基本规则
规则1 :结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员大小的整数倍的地方(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。
规则2:如果一个结构体B里嵌套另一个结构体A,则结构体A应从offset为A内部最大成员的整数倍的地方开始存储。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。
Tips:
结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。
不是直接将结构体A的成员直接移动到结构体B中
规则3:结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。(在所有结构体成员的字节长度都没有超出操作系统基本字节单位(32位操作系统是4,64位操作系统是8)的情况下,按照结构体中字节最大的变量长度来对齐;若结构体中某个变量字节超出操作系统基本字节单位,那么就按照系统字节单位来对齐。)
————————————————
版权声明:本文为CSDN博主「annjeff」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/annjeff/article/details/89227976
字节对齐
C语言中的数据对齐
什么是字节对齐,为什么要对齐?
c语言struct结构体强制类型转换
字节对齐规则
C语言字节对齐问题详解
结构体类型变量的定义方法
先定义结构体类型再定义变量名
这是C语言中定义结构体类型变量最常见的方式
struct 结构体名
{
成员列表;
};
struct 结构体名 变量名;
Example:
定义几个职工变量:struct worker
{
long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20];
};
struct worker worker1,worker2;
注意事项:
“struct worker”代表类型名,不能分开写为:
struct worker1,worker2; //错误,没有指明是哪种结构体类型
或 worker worker1,worker2; //错误,没有struct关键字,系统不认为worker是结构体类型
为了使用上的方便,程序员通常用一个符号常量代表一个结构体类型。即在程序开头加上下列语句:
#define WORKER struct worker;
这样在程序中,WORKER与struct worker完全等效。
Example:
WORKER
{ long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20]; };
WORKER worker1,worker2;
此时,可以直接用WORKER定义worker1、worker2两个变量,而不必再写关键字struct。
在定义类型的同时定义变量
这种形式的定义的一般形式为:
struct 结构体名
{
成员列表;
}变量名;
Example:
struct worker
{ long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20];
} worker1,worker2;
直接定义结构类型变量
其一般形式为:
struct //没有结构体名
{
成员列表;
}变量名;
Example:
struct
{
long number;
char name[20];
char sex;
int age;
float salary;
char address[80];
char phone[20];
} worker1,worker2;
大小
一个结构体变量占用内存的实际大小,也可以利用sizeof求出。它的运算表达式为:
sizeof(运算量)//求出给定的运算量占用内存空间的字节数
其中运算量可以是变量、数组或结构体变量,可以是数据类型的名称。
例如:
sizeof(struct worker)
sizeof(worker1)
结构体变量的使用形式
结构体变量是不同数据类型的若干数据的集合体。在程序中使用结构体变量时,一般情况下不能把它作为一个整体参加数据处理,而参加各种运算和操作的是结构体变量的各个成员项数据。
结构体变量的成员用以下一般形式表示:
结构体变量名.成员名
例如,上节给出的结构体变量worker1具有下列七个成员:
worker1.number;worker1.name;worker1.sex;
worker1.age;worker1.salary;worker1.address;
worker1.phone
(1)在定义了结构体变量后,就可以用不同的赋值方法对结构体变量的每个成员赋值。例如:
strcpy(worker1.name,”Zhang San”);
worker1.age=26;
strcpy(worker1.phone,”1234567”);
worker1.sex=’m’;
:
:
除此之外,还可以引用结构体变量成员的地址以及成员中的元素。例如:引用结构体变量成员的首地址worker1.name;引用结构体变量成员的第二个字符worker1.name[1];引用结构体变量的首地址&worker1。
(2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算。例如,对上面定义的结构体类型变量worker1,可以这样访问各成员:
worker1.age
worker1.name
worker1.birthday.year
worker1.birthday.month
worker1.birthday.day
注意:不能用worker1.birthday来访问worker1变量中的成员birthday,因为birthday本身是一个结构体变量。
(3)对成员变量可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。例如:
worker2.age=worker1.age;
sum=worker1.age+worker2.age;
worker1.age++;
(4)在数组中,数组是不能彼此赋值的,而结构体类型变量可以相互赋值。
在C程序中,同一结构体类型的结构体变量之间允许相互赋值,而不同结构体类型的结构体变量之间不允许相互赋值,即使两者包含有同样的成员。
结构体变量的初始化
与其他类型变量一样,也可以给结构体的每个成员赋初值,这称为结构体的初始化。一种是在定义结构体变量时进行初始化,语法格式如下:
struct 结构体名 变量名={初始数据表};
另一种是在定义结构体类型时进行结构体变量的初始化。
struct 结构体名
{
成员列表;
}变量名={初始数据表};
前述student结构体类型的结构体变量wan在说明时可以初始化如下:
struct student wan={”Wan Jun”,’m’,20,” SuZhou Road No.100”};
等价于下列代码:
strcpy(wan.name,” Wan Jun”);
wan.sex = ’m’;
wan.age = 20;
strcpy(wan.addr, ” SuZhou Road No.100”);
指向结构体类型变量的指针变量
一、指向结构体类型变量的指针变量
定义指向结构体类型变量的指针变量和定义结构体类型变量的方法基本相同,区别是在结构体
类型指针变量名的前面加一个可以分别定义结构体类型和指针变量,也可以同时定义结构体类型和
相应的指针变量,其中后者还可以省略结构体类型名。
通过指向结构体类型变量的指针变量来访问结构体类型变量的成员,与直接使用结构体类型变
量的作用相同。一般地,如果指针变量 pointei 已指向结构体类型变量 var,则以下三种形式等价:
方式一:var.成员
方式二:pointer->成员
方式三:(*pointer).成员 /*“*pointer”外面的一对圆括号不能省略*/
需要注意以下几点:
1)方式一中,成员运算符“.”左侧的运算对象只能是结构体类型变量。
2)方式二中的“->”称为指向成员运算符或简称为指向运算符。该运算符是双目运算符,其
左侧的运算对象必须是结构体类型变量(或数组元素)的地址,也可以是已指向结构体类型变量(或
数组元素)的指针变量,否则会出错;右侧的运算对象必须是该结构体类型的成员名。指向运算符
的优先级和“( )”“[ ]”“.”是同级的,结合性是从左至右。
3)方式三中的“*指针变量”代表了它所指向的结构体类型变量,利用成员运算符“.”来引用,
等价于“结构体类型变量.成员名”。“*指针变量”必须用圆括号括起来,因为运算符“*”的优先
级低于运算符“.”,若没有圆括号则优先处理运算符“.”,将出现“指针变量.成员名”会造成语法
错误
结构体数组
1. 先定义结构体类型,再用它定义结构体数组。
2. 在定义结构体类型同时定义结构体数组。
3. 直接定义结构体数组
结构体数组的初始化
结构体数组在定义的同时也可以进行初始化,并且与结构体变量的初始化规定相同,只能对全局的或静态存储类别的结构体数组初始化。
结构体数组初始化的一般形式是:
struct 结构体名
{
成员列表;
};
struct 结构体名 数组名[元素个数]={初始数据表};
或者:
struct 结构体名
{
成员表列;
}数组名[元素个数]={初始数据表};
由于结构体变量是由若干不同类型的数据组成,而结构体数组又是由若干结构体变量组成。所以要特别注意包围在大括号中的初始数据的顺序,以及它们与各个成员项间的对应关系。
结构体数组的使用
一个结构体数组的元素相当于一个结构体变量,因此前面介绍的有关结构体变量的规则也适应于结构体数组元素。以上面定义的结构体数组stu[3]为例说明对结构体数组的引用:
(1)引用某一元素中的成员。
若要引用数组第二个元素的name成员,则可写为:
stu[1].name
(2)可以将一个结构体数组元素值赋给同一结构体类型的数组中的另一个元素,或赋给同一类型的变量。如:
struct student stu[3],student1;
现在定义了一个结构体类型的数组,它有3个元素,又定义了一个结构体类型变量student1,则下面的赋值是合法的。
student1=stu[0];
stu[0]=stu[1];
stu[1]=student1;
(3)不能把结构体数组元素作为一个整体直接进行输入输出。如:
printf(“…”,stu[0]);
或
scanf(“…”,&stu[0]);
都是错误的。
只能以单个成员为对象进行输入输出,如:
scanf(“…”,stu[0].name);
scanf(“…”,&stu[1].num);
printf(“…”,stu[0].name);
printf(“…”,stu[1].num);
结构体指针
可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。
结构体指针与前面介绍的各种指针变量在特性和方法上是相同的。与前述相同,在程序中结构体指针也是通过访问目标运算“*”访问它的对象。结构体指针在程序中的一般定义形式为:
struct 结构体名 *结构指针名;
其中的结构体名必须是已经定义过的结构体类型。
例如,
对于上一节中定义的结构体类型struct student,可以说明使用这种结构体类型的结构指针如下:
struct student *pstu;
其中pstu是指向struct student结构体类型的指针。结构体指针的说明规定了它的数据特性,并为结构体指针本身分配了一定的内存空间。但是指针的内容尚未确定,即它指向随机的对象。
当表示指针变量p所指向的结构体变量中的成员时,“(*结构体指针名).成员名”这种表示形式总是需要使用圆括号,显得很不简炼。因此,对于结构体指针指向的结构体成员项,给出了另外一种简洁的表示方法,如下表示:
结构体指针名->成员名
它与前一种表示方法在意义上是完全等价的。例如,结构体指针p指向的结构体变量中的成员name可以表示如下:
(*p).name 或 p->name
共用体
在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。
共用体在定义、说明和使用形式上与结构体相似。两者本质上的不同仅在于使用内存的方式上。
定义一个共用体类型的一般形式为:
union 共用体名
{
成员表列;
};
共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。
就里定义了一个共用体类型union gy,它由三个成员组成,这三个成员在内存中使用共同的存储空间。由于共用体中各成员的数据长度往往不同,所以共用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。
在这一点上共用体与结构体不同,结构体类型变量在存储时总是按各成员的数据长度之和占用内存空间。
例如,定义了一个结构体类型:
struct gy
{
int i;
char c;
float f;
};
则结构体类型struct gy的变量占用的内存大小为2+1+4=7个字节(不考虑字节对齐 。
在使用共用体类型变量的数据时要注意:在共用体类型变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。如有以下赋值语句:
a.i = 1;
a.c = ’a’;
a.f = 1.5;
完成以上三个赋值运算以后,a.f是有效的,a.i和a.c已经无意义了。
在程序中经常使用结构体与共用体相互嵌套的形式。
即共用体类型的成员可以是结构体类型,或者结构体类型的成员是共用体类型。
例如,下列结构体类型datas的第三个成员是共用体类型:
struct datas
{
char *ps;
int type;
union
{
float fdata;
int idata;
char cdata;
}udata;
};
在C语言中,允许使用关键字typedef定义新的数据类型
在C语言中,允许使用关键字typedef定义新的数据类型
其语法如下:
typedef <已有数据类型> <新数据类型>;
如:
typedef int INTEGER;
这里新定义了数据类型INTEGER, 其等价于int
INTEGER i; <==> int i;
在C语言中经常在定义结构体类型时使用typedef,例如
typedef struct _node_
{
int data;
struct _node_ *next;
} listnode, *linklist;
这里定义了两个新的数据类型listnode和linklist。其中listnode
等价于数据类型struct node 而 linklist等价于struct node *
内存管理
存储模型(c基础补习中已讲)
C/C++定义了4个内存区间:
代码区/全局变量与静态变量区/局部变量区即栈区/动态存储区,即堆区
内存管理
静态存储分配
通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。
在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
动态存储分配
有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种方法称为。
所有动态存储分配都在堆区中进行。
从堆上分配,亦称动态内存分配。程序在运行的时候用malloc1申请任意多少的内存,程序员自己负责在何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
堆内存的分配与释放
当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。
堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式(initializer)来显式初始化。
malloc/free
void * malloc(size_t num)
void free(void p) malloc
函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。
malloc返回值的类型是void ,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
如果free的参数是NULL的话,没有任何效果。
释放一块内存中的一部分是不被允许的。
注意事项:
删除一个指针p(free§;),实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针
动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。
malloc与free是配对使用的, free只能释放堆空间。如果malloc返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏
,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。
动态分配的变量或对象的生命期。无名对象的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后仍可使用。我们也称堆空间为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。
野指针:
不是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。
“野指针”的成因主要有两种:
1.指针变量没有被初始化。
2.指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。指针操作超越了变量的作用范围。这种情况让人防不胜防。
运算符
-
算术运算符
flat double 不能取余 -
逻辑运算符
-
位运算符
-
关系运算符 左移一位相当于乘2
-
赋值运算符
赋值运算符为“=”,其运算的一般形式如下:
<左值表达式> = <右值表达式> -
复合 运算符
赋值复合运算符其运算的一般形式如下:
<变量> <操作符>= <表达式> -
C运算符的优先级
逗号运算符,最后运算结果进行赋值
24. 优先处理两个=
D3输入输出专题
字符输出函数和格式输出函数
-
c语言无I/O语句,I/O操作由函数实现
-
函数: 功能 参数 返回值
-
putchar(int arg);
字符输出函数
格式: putchar( c )
参数: c为字符常量、变量或表达式
功能:把字符c输出到显示器上
返值:正常,为显示的代码值; -
printf("格式控制串“,输出表)
格式符p 作用是输出地址
-
附加格式符
-
m 整数部分指定位数 .n 指定保留小数位
-
10 进制转二进制,整数部分除基取余,直到商小于1,小数部分乘基取整,直到小数部分为0
-
getchar(); 返回值int .错误返回-1。char 范围小,unsight char(),ctrl d 结束输入
-
scanf(“格式控制串”,地址表),返回值,用户输入变量个数
10.修饰符
-
char * gets(char *s); 注意参数是字符串,把输入当字符串处理。
功能:从键盘输入一以回车结束的字符串放入字符数组中,并自动加‘\0’
说明1:输入串长度应小于字符数组维数
说明2:与scanf函数不同,gets函数并不以空格作为字符串输入结束的标志。因为这个函数不安全,从vs2015起gets()函数就没有了,因为可能会造成缓冲区溢出, 甚至程序崩溃。故不推荐使用。vs2019会建议用gets_s()来代替
gets_s()是C11标准,由于C11标准还没有完全普及很多编译器还不支持C11标准。有些编译器还不支持C11标准,在这我们就用fgets代替gets。
//gets_s()用法
#include <stdio.h>
#define CH 20
int main(void)
{
char ch[CH];
printf("请输入你的名字:\n");
//gets_s用法:gets_s(buffer,size);
//推荐用字符数组长度-1作为size(留空'\0')
gets_s(ch,CH-1);
printf("这是你的名字:%s\n", ch);
return 0;
}
————————————————
版权声明:本文为CSDN博主「归子莫」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45163122/article/details/104475955
- fgets()
函数原型:char *fgets(char *str, int n, FILE *stream);
参数:
str-- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
n-- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
stream-- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
功能
从指定的流 stream 读取一行,并把它存储在str所指向的字符串内。当读取(n-1)个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定。
例子
fgets(c, sizeof(c), stdin); //与gets(c)等效
-
int puts(char *s); 注意参数是字符串,输出一个字符串。自动换行
格式:int puts(const char *s)
功能:向显示器输出字符串(输出完,换行)
说明:字符数组必须以‘\0’结束 -
求平方根函数 sqrt(),使用该函数加<math.h>文件头 编译的时候gcc a.c 后加 -lm (链接选项)。
#include <math.h>
double sqrt(double x);
float sqrtf(float x);
long double sqrtl(long double x);
D4 控制语句
控制语句
- 调格式 gg=G
- if 判断里把常量写在前边
- 海伦公式 p=(a+b+c)/2
循环语句
-
loop: … goto loop; 实现循环的时候,注意写变化语句如i++ ,注意写循环结束条件。
当函数有很多个出口,使用goto 把这些出口集中道一处很方便,特别是函数中有许多重复的清理工作的时候。
*无条件跳转易于理解
*可以减少嵌套
*可以避免那种忘记某一个出口点的问题
*算是帮助编译器做了代码优化 -
while 语句 while(){};
变量初值
终止条件
变量变化 -
do{ }while() ;
-
pow();求次方函数。使用该函数加<math.h>文件头 编译的时候gcc a.c 后加 -lm (链接选项)。
-
for();表达式1可以省略,表达式2可以省略,表达式3可以省略;
-
if-else语句
阶梯形式if语句
if(表达式1) 语句块1
else if(表达式2) 语句块2
else if(表达式3) 语句块3
else if(表达式4) 语句块4
…
else 语句块n -
switch语句
switch语句的基本形式
switch (表达式)
{ case 常量表达式1:语句块1;break;
case 常量表达式2:语句块2; break;
….
case 常量表达式n:语句块n; break;
default :语句块n+1
}
switch语句的使用:
每个常量表达式的值必须各不相同,否则将会出现矛盾。
当表达式的值与case后面的常量表达式值相等时,就执行此case后面的语句。
switch中的表达式可以是整型、字符型表达式或枚举。
case 常量:只起语句标号的作用。 -
1582年以来的置闰规则:
普通闰年:公历年份是4的倍数,且不是100的倍数的,为闰年(如2004年、2020年等就是闰年)。
世纪闰年:公历年份是整百数的,必须是400的倍数才是闰年(如1900年不是闰年,2000年是闰年)。
循环辅助语句
- break 跳出一层循环
- continue 结束一次循环
- 知识是掌握概念和原理,能力是熟练运用知识
D5数组和字符串
数组
- C语言对数组不做越界检查,使用时要注意,别太依赖编译器解决所有问题
- 必须先定义,后使用,不初始化,编译也不报错。不初始化,值随机 。static 数组元素不赋值,初值为0;
- 二维数组按行序优先
字符数组和字符串
- 字符数组赋值:可以逐个字符赋值,可以用字符串常量赋值
- 使用格式符s做输出字符数组是需要注意字符数组中是否有‘\0’结尾,否则可能越界输出其他内容;
- char[]={‘a’,‘b’,‘c’,‘\0’} ,在内存中占4个字节,字符长度3。
字符串函数
- strlen(dest);
格式:strlen(字符数组)
功能:计算字符串长度
返值:返回字符串实际长度,不包括‘\0’在内
例:对于以下字符串,strlen(s)的值为:
char s[10]={‘A’,‘\0’,‘B’,‘C’,‘\0’,‘D’};
char s[ ]=“\t\v\\0will\n”;//
char s[ ]=“\x69\141\n”; // \xhh表示十六进制数代表的符号 \ddd表示8进制的
答案:1 3 3 - strcpy(dest,src);
格式:strcpy(字符数组1,字符串2)
功能:将字符串2,拷贝到字符数组1中去
返值:返回字符数组1的首地址
说明:
字符数组1必须足够大
拷贝时‘\0’一同拷贝 - strncpy(p, p1, n) 复制指定长度字符串
- strcat(dest,src );//函数在 C 语言中被认为是不安全的,因为它没有检查目标字符串的空间是否足够,以容纳源字符串。如果目标字符串的空间不足以容纳源字符串,则会导致缓冲区溢出,从而导致软件缺陷和安全漏洞。为了避免这些问题,建议使用安全的字符串函数,如 strncat() 和 strlcpy()。
格式:strcat(字符数组1,字符数组2)
功能:把字符数组2连到字符数组1后面
返值:返回字符数组1的首地址
说明:
字符数组1必须足够大
连接前,两串均以‘\0’结束;连接后,串1的 ‘\0’取消,新串最后加‘\0’ - strncat(p, p1, n) ;附加指定长度字符串
- strcmp(c1,c2); 返回值为整数
字符串比较函数strcmp
格 式:strcmp(字符串1,字符串2)
功 能:比较两个字符串
比较规则:对两串从左向右逐个字符比较
(ASCII码),直到遇到不同字符或‘\0’为止
返 值:返回int型整数
a. 若字符串1< 字符串2, 返回负整数
b. 若字符串1> 字符串2, 返回正整数
c. 若字符串1== 字符串2, 返回零 - strncmp(c1,c2,4); 返回值为整数,只比较前四位
- strcasecmp(c1,c2); 忽略大小写
- strchr(dest, ch);返回值是ch下标
- strrchr(dest, ch);返回值是倒数第一个ch下标
- strstr(s,sub);返回一个子串地址
- strchr(p, c) 在字符串中查找指定字符
- strstr(p, p1) 查找字符串
- 校验,
isalpha(ch) 是否为字母 <ctype.h>
isupper(ch)检查是否为大写字母字符
islower(ch) 检查是否为小写字母字符 - toupper();返回值是转换后的
- isdigit() 检查是否为数字
- int atoi(const char *s); 字符串转数字
- char *itoa(int i,char *s,int radix); 数字转字符串
- strtok()函数,将字符串分解为一组字符串
D6 指针专题一
指针的基本用法
- 功能
使程序简洁
设计数据结构
动态分配内存
多于一个返回函数 - 地址和变量
在计算机内存中,每一个字节单元,都有一个编号,称为地址。
变量是对程序中数据存储空间的抽象。
在C语言中,内存单元的地址称为指针,专门用来存放地址的变量,称为指针变量
在不影响理解的情况中,有时对地址、指针和指针变量不区分,通称指针
32 位地址宽度内存指针占四个字节
一般形式如下:
<存储类型> <数据类型> * <指针变量名> ;
例如,char *pName ;
指针的存储类型是指针变量本身的存储类型。
指针声明时指定的数据类型不是指针变量本身的数据类型,而是指针目标的数据类型。简称为指针的数据类型
-
指针指向的内存区域中的数据称为指针的目标,声明该目标的变量称为目标变量.
引入指针要注意程序中的px、*px 和 &px 三种表示方法的不同意义。设px为一个指针,则:
px — 指针变量, 它的内容是地址量
*px — 指针所指向的对象, 它的内容是数据
&px — 指针变量占用的存储区域的地址,是个常量 -
赋值
把一个普通变量的地址赋给一个具有相同数据类型的指针
double x=15, *px;
px=&x;
把一个已有地址值的指针变量赋给具有相同数据类型的另一个指针变量
float a, *px, *py;
px = &a;
py = px;
把一个数组的地址赋给具有相同数据类型的指针。例如:
int a[20], *pa;
pa = a; //等价 pa = &a[0]
指针的运算
-
指针运算是以指针变量所存放的地址量作为运算量而进行的运算
-
指针运算的实质就是地址的计算
-
只能进行赋值运算、算术运算和关系运算
-
指针的算术运算
-
注意
不同数据类型的两个指针实行加减整数运算是无意义的
px+n表示的实际位置的地址量是:
(px) + sizeof(px的类型) * n
px-n表示的实际位置的地址量是:
(px) - sizeof(px的类型) * n
sizeof(),是C语言中的一个运算符,用来计算某个数据类型或某个变量在内存中占用的字节数(byte) -
两指针相减运算
px-py 运算的结果是两指针指向的地址位置之间相隔数据的个数。因此,两指针相减不是两指针持有的地址值相减的结果。
两指针相减的结果值不是地址量,而是一个整数值,表示两指针之间相隔数据的个数。 -
指针关系运算
两指针之间的关系运算表示它们指向的地址位置之间的关系。指向地址大的指针大于指向地址小的指针。
指针与一般整数变量之间的关系运算没有意义。但可以和零进行等于或不等于的关系运算,判断指针是否为空。 -
* 比 ++ 的优先级高
指针与数组
-
在C语言中,数组的指针是指数组在内存中的起始地址,数组元素的地址是指数组元素在内存中的起始地址
一维数组的数组名为一维数组的指针(起始地址)
例如
double x[8];
因此,x为x数组的起始地址 -
设指针变量px的地址值等于数组指针x(即指针变量px指向数组的首元数),则:
x[i] 、(px+i)、(x+i) 和px[i]具有完全相同的功能:访问数组第i+1个数组元素。
-
指针变量和数组名都是地址量,但指针变量是地址变量而数组的指针是地址常量
int a[10] ,*p=a;
a 是地址常量,p 是地址变量
数据存储的空间中的数据可以被修改,这个空间称为变量,如果空间中的数据不能被修改,这个空间称为常量。地址常量就是地址不能被修改,就像一维数组中的数组名,是一个指针常量,不可被运算和不可被改变。地址变量就是地址能修改,就像一级指针,是一个指针变量,可以通过移动下标或移动指针来改变。C语言中,不是指针常量的是(D)
(A) 空指针(NULL)
(B) 函数的名字
(C) 数组的名字
(D) 宏函数的名字
例:int a[]={1,2,3,4,5,6,7,8,9,10}, *p = a, i;数组元素地址的正确表示是:
(A)&(a+1) (B)a++ (C)&p D)&p[i]
数组名是地址常量
p++,p-- (对)
a++,a-- (错)
a+1, *(a+2) (对)
指针与二维数组
-
多维数组就是具有两个或两个以上下标的数组
在C语言中,二维数组的元素连续存储,按行优先存
-
可把二维数组看作由多个一维数组组成。
比如int a[3][3],含有三个元素:a[0]、a[1]、a[2]
元素a[0]、a[1]、a[2]都是一维数组名
-
二维数组名代表数组的起始地址,数组名加1,是移动一行元素。二维数组名常被称为行地址(*行地址,可以将行级指针降级成一级指针或者说列级指针)。
-
行指针(数组指针)
存储行地址的指针变量,叫做行指针变量。形式如下:
<存储类型><数据类型>(*<指针变量名>)[表达式];
example :
- int a[2][3];
- int (*p)[3]; //对应行指针变量
- p=a;
方括号中的常量表达式表示指针加1,移动几个数据。当用行指针操作二维数组时,表达式一般写成1行的元素个数,即列数。
D7 指针专题2
字符指针与字符串
- C语言通过使用字符数组来处理字符串,C语言通过使用字符数组来处理字符串。通常,我们把char数据类型的指针变量称为字符指针变量。字符指针变量与字符数组有着密切关系,它也被用来处理字符串。
- 初始化字符指针是把内存中字符串的首地址赋予指针,并不是把该字符串复制到指针中
char str[] = “Hello World”;
char *p = str; - 在c编程中,当一个字符指针指向一个字符串常量时,不能修改指针指向的对象的值
char * p = “Hello World”;
*p = ‘h’; // 错误, 字符串常量不能修改 - 放到静态区域中的变量:全局变量、static 变量、字符串常量。程序结束之后才释放内存 。
指针数组
- 指针数组: 由若干个具有相同存储类型和数据类型的指针变量构成的集合。
<存储类型> <数据类型> *<指针数组名> [<大小>]
声明一个指针数组:
double * pa[2] ,a[2][3];
把一维数组a[0]和a[1]的首地址分别赋予指针变量数组的数组元数pa[0]和pa[1]:
pa[0]=a[0] ; // 等价pa[0] = &a[0][0];
pa[1]=a[1]; // 等价pa[1] = &a[1][0];
此时pa[0]指向了一维数组a[0]的第一个元素a[0][0], 而pa[1]指向了一维数组a[1]的第一个元素a[1][0]。
- 32位处理器,一个指针占4个字节
- 指针数组名加1,移动4个字节
多级指针
- 多级指针的定义
把一个指向指针变量的指针变量,称为多级指针变量
对于指向处理数据的指针变量称为一级指针变量,简称一级指针
而把指向一级指针变量的指针变量称为二级指针变量,简称二级指针
二级指针变量的说明形式如下
<存储类型> <数据类型> ** <指针名> ; - 多级指针的运算
指针变量加1,是向地址大的方向移动一个目标数据。类似的道理,多级指针运算也是以其目标变量为单位进行偏移。
比如,int **p;p+1移动一个int *变量所占的内存空间。再比如int ***p,p+1移动一个int **所占的内存空间。 - 多级指针和指针数组
指针数组也可以用另外一个指针来处理。
例如:有一个一维字符指针数组ps[5],
char *ps[5]= { “Beijing city”,
……
“London city” } ;
定义另一个指针变量pps,并且把指针数组的首地址赋予指针pps
char *ps[5]={……}; char ** pps = ps;
void指针和const修饰符
- void 指针是一种不确定数据类型的指针变量。
它可以通过强制类型转换让该变量指向任何数据类型的变量。
一般形式为: void * <指针变量名称> ;
没有转换之前不能进行任何指针的算术运算
void * malloc(size_t size);
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));
- const变量
常量化变量的值
一般说明形式:
const <数据类型> 变量名 = [<表达式>];
常量化变量是为了使得变量的值不能修改
常量化指针目标表达式
一般说明形式如下:
const <数据类型> * <指针变量名称> = [<指针运算表达式>] ;
能修改<指针变量>存储的地址值,不可以通过 *<指针变量名称> 修改指针所指向变量的数值
常量化指针变量
一般说明形式如下:
<数据类型> * const <指针变量名称> = [<指针运算表达式>] ;
不能修改<指针变量>存储的地址值,可以通过 *<指针变量名称> 修改指针所指向变量的数值
常量化指针变量及其目标表达式
一般说明形式如下:
const <数据类型> * const <指针变量名> = <指针运算表达式> ;
常量化指针变量及其目标表达式,使得既不能修改<指针变量>存储的地址值,也不可以通过 *<指针变量名称> 修改指针所指向变量的数值
const 放在 * 右边意味着指针变量存储的地址值不能修改,但可以通过*<指针变量>修改指针变量所指向变量的数值。
const 放在 * 左边意味着不能通过指针改目标变量的值。
参考1
参考2
3. main 函数参数的作用
D8 函数
函数的基本用法
- 函数
main函数,是整个程序的入口
库函数,直接引入调用 - 函数先说明再实现,也可以实现和声明分开来写,写再调用之前
- #includ 作用是实现函数声明,不引入头文件引入函数声明也行
函数的参数传递
- 传递方式
全局变量,
全局变量就是在函数体外说明的变量,它们在程序中的每个函数里都是可见的。
全局变量一经定义后就会在程序的任何地方可见。函数调用的位置不同,程序的执行结果可能会受到影响。不建议使用
复制传递方式(值传递),
不能改变实参是值,形参是新开辟的存储空间
地址传递方式,
按地址传递,实参为变量的地址,形参为同类型的指针,被调用函数对形参的操作,将直接改变实参的值。
功能决定了函数如何去设计
函数中传递数组参数
- 全局数组传递方式
- 复制传递方式:实参为数组的指针,形参为数组名(本质是一个指针变量 )
- 地址传递方式:实参为数组的指针,形参为同类型的指针变量。
指针函数
- 指针函数:指针函数是指一个函数的返回值为地址量的函数
注意返回值不能是一个局部变量,注意内存管理。
指针函数的定义的一般形式如下
<数据类型> * <函数名称>(<参数说明>) {
语句序列;}
返回值:全局变量的地址/static变量的地址
/字符串常量的地址/堆的地址
返回值有效的方案:全局变量的地址、static 变量的地址、字符串常量的地址、(动态内存-堆上的地址)
递归函数
- 递归函数
递归函数是指一个函数的函数体中直接或间接调用了该函数自身
递归函数调用的执行过程分为两个阶段:
递推阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件
回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归到原问题求解 - 递归能否实现的判断
- 需要有完成函数任务的语句
- 一个确定的能否避免递归调用的测试
- 一个递归调用语句
- 先测试后递归调用
函数指针
- 函数指针
函数指针用来存放函数的地址,这个地址是一个函数的入口地址
函数名代表了函数的入口地址
函数指针变量说明的一般形式如下
<数据类型> (<函数指针名称>)(<参数说明列表>);
<数据类型>是函数指针所指向的函数的返回值类型
<参数说明列表>应该与函数指针所指向的函数的形参说明保持一致
(<函数指针名称>)中,*说明为指针()不可缺省,表明为函数的指针
//函数指针声明及调用演变过程
int (int a,int b) * pf;//语法错误
int *pf(int a,int b);//函数声明语句
int (*pf)(int a,int b);//定义一个函数指针
int (*pf)(int ,int );//定义一个函数指针
pf = &func;//&运算符后面如果是函数名的话可以省略不写
pf = func;
y = func(3,4);//直接调用
y = (*pf)(3,4);//间接调用,*运算符后面如果是函数指针类型则可以省略不写
y = pf(3,4);//间接调用
typedef int myint; //正常定义别名形式
typedef int (*)(int,int) pft;//语法错误
typedef int (*pft)(int,int) ;
pft pt;
#include <stdio.h>
int test(int a,int b,int (*pFunc)(int,int));
int plus(int a,int b);
int miinut(int ,int);
int main(int argc, char *argv[])
{
int x = 5,y = 8;
int(*pFunc)(int,int);
pFunc = plus;
printf("%d\n",(*pFunc)(x,y));
pFunc = miinut;
printf("%d\n",(*pFunc)(x,y));
printf("%d\n",test(x,y,plus));
printf("%d\n",test(x,y,miinut));
return 0;
}
int plus(int a,int b)
{
return a+b;
}
int miinut(int a,int b)
{
return a-b;
}
int test(int a,int b,int(*pFunc)(int,int))
{
return ((*pFunc)(a,b));
}
函数指针数组
是一个保存若干个函数名的数组
一般形式如下
<数据类型> (*<函数指针数组名称> [<大小>] )(<参数说明列表> );
其中,<大小>是指函数指针数组元数的个数
其它同普通的函数指针
int (*p[4])(int ,int);
回调函数
回调函数的形参是函数指针,需要传递一个函数的入口地址。
注脚
每个进程会有两个栈 ,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。虽然栈内存空间在Win系统下分配为1M(Linux环境下有操作系统决定,一般是8M,通过ulimit命令查看以及修改。Windows环境下由编译器决定,VC++6.0一般是1M),但是内核栈会占用一部分内存空间,导致栈的内存空间会小于1M,所以,当开辟1M内存空间时,自然会开辟失败。
此时在栈空间不够用的情况下,选用堆区进行内存空间开辟(即动态内存开辟),而堆区与栈区不同,堆区内存由程序员通过相应函数进行内存申请分配,在使用结束后,需要程序员通过free()函数进行手动释放。
————————————————
版权声明:本文为CSDN博主「Conaldo7」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_57104108/article/details/126434043 ↩︎