函数补充
可变长参数列表:
例:
有三个点(...)
int printf(const char *format,...);
int scanf(const char *format,...);
第一步:声明va_list变量
va_list ap;
第二步:初始化
va_start(ap,形参列表中最后一个形参名last);
第三步: 获取值 每调用一次该方法会获得一个参数 type是参数的数据类型
type x = va_arg(ap,type)
第四步: 关闭资源
va_end(ap);
函数参数的传递过程:()
(1)实参和形参可以同名,也可以不同名,但是对应位置的类型得一致,一致并不代表完全一模一样,一致可以是隐式类型转换,所以如果类型不一致,编译时将报错
(2)形参在函数调用时被初始化了,是用实参的值初始化
(3)实参和形参是不同的两个变量 它们只是在调用初始化值 一样而已
(4)C语言中函数传递的过程是值传递的过程 即复制
(5)对于普通数据类型 在函数中修改形参的值 并不会影响实参的值
注意:对于数组而言,在函数中修改数组中的元素 会影响原来实参数组的值,对于指针而言,在函数中修改指针所指向的值,会影响原来实参数组的值。
(6)关于定义函数时不让函数修改形参的值
形参类型前加 const 表示在该函数中不能对形参的值进行修改
一般情况下 修改 数组 才有意义
例:func(const int arr[],size_t len) 数组中的元素在该函数中不能被修改
const作用:
形参加const,防止在函数中意外地修改
增加代码的健壮性 和 可读性
const修饰的变量,表示 只读 不可以修改
内存地址:
声明变量 这个变量其实代表一块内存区域
操作变量 其实操作的是这片内存区域里存储的值
对于变量可以进行取址(&)运算,取得这个变量所代表的内存地址
对于取址运算符(&) 操作数只有一个,且只能是左值
%p 可以输出一个地址 地址:一个编号 一个十六进制的整数
内存地址即指针
指针变量 可以 保存 内存地址
一个程序中有多少个地址? 4G
2^32个地址
2^10 * 2^10 * 2^10 *2^2
4 * 1024*1024*1024 Byte = 4G
每一个运行的C语言程序在内存中都有4G的虚拟内存 0-0xffffffff
4G虚拟内存:
从低到高地址依次为: 代码段、全局区、堆区、栈区
代码区: 代码指令 字面值字符串
静态全局区:
数据段: 已经初始化的全局变量和已经初始化的静态变量
BSS段: 未初始的全局变量 和 未初始化的静态变量
堆区: 动态内存
栈区: 局部变量 函数调用开销
关于4G虚拟内存之后的补充:
进程在内存空间中的布局就是进程映像,从低地址到高地址依次为:
代码区(text): 可执行指令、字面值常量、具有常属性的全局和静态局部 变量。只读。
数据区(data):初始化的全局和静态局部变量。
BSS区: 未初始化的全局和静态局部变量。 进程一经加载此区即被清0。 数据区和BSS区有时被合称为全局区或静态区。
堆区(heap): 动态内存分配。从低地址向高地址扩展。
栈区(stack): 非静态局部变量, 包括函数的参数和返回值。 从高地址向低地址扩展。 堆区和栈区之间存在一块间隙,一方面为堆和栈的增长预 留空间,同时共享库、共享内存等亦位于此。
命令行参数与环境区: 命令行参数和环境变量。
变量:
块变量:定义在语句块中的变量
(1)存储位置:栈区
(2)生命周期:语句块执行阶段
语句块执行时生成,语句块结束消亡
(3)作用域:在定义块变量开始位置到语句块结束位置
语句块 { }
注:在同一个作用域下面,变量名不能相同
局部变量:定义在函数中的变量(形参)
(1)存储位置: 栈区
(2)生命周期: 函数调用时创建,函数返回时消亡
(3)作用域: 局部变量定义位置以后至函数结束
全局变量:定义在全局位置(在函数外)的变量
(1)存储位置:全局区
(2)生命周期:整个程序运行期间
(3)作用域:所有代码文件
局部变量和全局变量:
(1)局部变量如果不初始化是随机值(垃圾值)
全局变量即使不初始化 也会 自动初始化为0
(2)局部变量优先原则:
局部变量可以和全局变量同名
在访问同名变量时局部变量会隐藏掉全局变量
块变量可以和局部变量、全局变量同名 就近原则
变量的修饰:
auto 自动的 默认是auto 可以省略,一般不写
static 静态的
(1)static修饰局部变量 、块变量
a.存储位置:局部变量的存储位置发生了变化,由栈区变为全局区
b.生命周期:静态局部变量的生命周期为整个程序
c.作用域: 没有发生变化
静态局部变量的声明定义语句在整个程序过程中只会执行一次
静态局部变量的值不会随着函数的调用而消失
(2)static修饰全局变量
a.存储位置: 没有变化
b.生命周期: 没有变化
c.作用域: 静态全局变量只能在本文件中访问
(3)static修饰函数
表示该函数只能在本文件中调用
const 只读的
修饰变量表示只读的 不可以修改
作用:
(1)修饰形参,防止在函数中意外修改
(2)增加代码的健壮性 和 可读性
register 寄存器变量 不能取址
申请把变量作为寄存器变量
如果一个变量经常使用 或者 一直在使用
仅仅是一种申请,编译器可能拒绝
(1) 寄存器要求数据必须是(32位) int
(2) 寄存器变量不可以取地址 &
volatile 易变的 修饰的变量表示可能随时发生变化
多线程的情况下
volatile int x = 10;
printf("%d\n",x*x);//有可能不是等于100 10
int y = x;
printf("%d\n",y*y);
extern 声明一个在其它文件中定义的全局变量
指针
指针定义:
指针即内存地址
指针变量 即 保存内存地址的变量
内存地址(虚拟内存 -- 物理内存): 0-4G 一个十六进制的整数
%p
指针变量的声明:
数据类型 *指针变量名;
* 不能省 表示指针类型
数据类型: 指针变量存储的内存地址,该内存地址所存储的数据的类型
指针变量的初始化:
& 取址符 作为取址运算符时,操作数必须是左值
数据类型 *指针变量名 = &变量;
操作符 *
取值运算符
*(内存地址) 取内存地址中保存的数据
*指针变量 取指针变量保存的那个地址里面的数据
操作符 &
取地址运算符
*&x == x 相互抵销
例:
int x = 10;
int *p = &x;
p == &x
*p == *(&x) == x == 10
*p = 100; //相当于改变了x的值
int y = 10;
p = &y; //指针变量保存了y的地址
*p = 110; //改变的是y的值
栈内存 从大往小使用
堆内存 从小往大使用
地址+1 本质上加了一个单位长度(数据类型的长度)的地址内存
地址本身虽然只是一个十六进制的整数,但其实地址自带类型
char c = 'a';
char *pc = &c;
short s = 30;
short *ps = &s;
指针类型
数据类型 * 是一个整体 表示的是地址的类型
若char c = 'a';
则char *pc = &c;
若int x = 1;
则int *px = &x;
指针变量的定义:
数据类型 * 标识符;
一般以p开头
就像以下,看见首字母能知道它的类型
int iAge;
char cSex;
long lDistance;
指针变量的初始化:
int x = 1;
int *px = &x;
若不知道它保存什么地址:
int *px = NULL;
NULL:
表示一个特殊的内存地址 0
这个0地址 保存的是代码
代码区的数据只读
一般来说指针初始化都为NULL
野指针:
一个指针变量没有初始化 指针里面的值为垃圾值 因为存储在指针变量里
所以看作一个内存地址
如果对该指针进行取*操作,将发生难以预测的后果 (灾难性的)
所以在编程过程中一定要避免野指针
悬空指针:
NULL
对于悬空指针不能进行取*操作,一旦取*,必然是核心已转储 段错误
所以一般在对指针进行取*操作前,都要对指针进行悬空判断
if(p != NULL){
*p
}
指针经典的一个应用,用函数交换两个值:
C语言中的函数的参数传递是值传递,值拷贝
形参和实参是两个不同的变量
只是在调用时会把实参的值赋值给形参
所以如果需要在函数中修改实参的值 那必须是传递地址
void swap(int a,int b){//只是交换了形参的值 交换不了实参的值
int tmp = a;
a = b;
b = tmp;
}
void swap(int *pa,int *pb){//交换了形参的值 交换了指针变量的值
int *pt = pa;
pa = pb;
pb = pt;
}
//野指针 非常危险
void swap(int *pa,int *pb){
int *pt;
*pt = *pa;
*pa = *pb;
*pb = *pt;
}
//正确
void swap(int *pa,int *pb){
int tmp = *pa;
*pa = *pb;
*pb = tmp;
}