标签:
指针基础
变量回顾
既然程序中的变量只是一段存储空间的别名 , 那么是不
是必须通过这个别名才能使用这段存储空间 ?
指针本质
? 指针在本质上也是一个变量
? 指针需要占用一定的内存空间
? 指针用于保存内存地址的值
* 号的意义
? 在指针声明时 ,* 号表示所声明的变量为指针
? 在指针使用时 ,* 号表示取指针所指向的内存空间中的值
// 指针声明 :
int i = 0;
int j = 0;
int* p = &i;
// 取值 :
j = *p;
p: 0xAABBCCD0 i: 0
0xAABBCCD0
* 号类似一把钥匙 , 通过这把钥匙可以
打开内存 , 读取内存中的值 。
1. 指针占用的内存空间
2. 指针的地址
3. 通过* 号写内存
传值调用与传址调用
? 指针是变量 , 因此可以声明指针参数
? 当一个函数体内部需要改变实参的值 , 则需要使用指针参数
? 函数调用时实参值将复制到形参
? 指针适用于复杂数据类型作为参数的函数中
常量与指针(判断const修饰的是p还是*p)
? const int* p; //p 可变 ,p 指向的内容不可变
? int const* p; //p 可变 ,p 指向的内容不可变
? int* const p; //p 不可变 ,p 指向的内容可变
? const int* const p; //p 和p 指向的内容都不可变
口诀 : 左数右指
当const 出现在* * 号左边时指针指向的数据为常量
当const 出现在* * 后右边时指针本身为常量
www.enjoylinux.cn
指针小结
? 指针是C 语言中一种特别的变量
? 指针所保存的值是内存的地址
? 可以通过指针修改内存中的任意地址内容
int* p
0x00eaff00 10 …… …… ……
0x00eaff00
数组基础
数组的概念
数组是相同类型的变量的有序集合
int a[5];
a代表数组第一个元素的起始地址。
这20个字节空间的名字为
a。a[0], a[1]等都是a中的
元素,并非元素的名字。数
组中的元素没有名字。
每个元素都是int型数据
数组的大小
? 数组在一片连续的内存空间中存储元素
? 数组元素的个数可以显示或隐式指定
数组地址与数组名
? 数组名代表数组首元素的地址,是个常量(整数),因此,不能作为左值
? 数组的地址需要用取地址符 & 才能得到
? 数组首元素的地址值与数组的地址值相同
? 数组首元素的地址与数组的地址是两个不同的概念
你家所在的楼房和你家的 GPS 地址是
相同的 , 但意义相同吗 ?
数组名的盲点
? 数组名可以看做一个常量指针
? 数组名“ 指向” 的是内存中数组首元素的起始位置
? 在表达式中数组名只能作为右值使用
? 只有在下列场合中数组名不能看做常量指针
? 数组名作为sizeof 操作符的参数
? 数组名作为& 运算符的参数
数组小结
? 数组是一片连续的内存空间
? 数组的地址和数组首元素的地址意义不同
? 数组名在大多数情况下被当成常量指针处理
? 数组名其实并不是指针 , 在外部声明时不能混淆
概念的混淆是 BUG 的根源之一 !
数组与指针分析
数组的本质
? 数组是一段连续的内存空间
? 数组的空间大小为sizeof(array_type) * array_size
? 数组名可看做指向数组第一个元素的常量指针
指针的运算
? 指针是一种特殊的变量 , 与整数的运算规则为
p + n; ?? (unsigned int)p + n*sizeof(*p);
结论 :
当指针p 指向一个同类型的数组的元素时 : p+1 将指向
当前元素的下一个元素 ; p- 1 将指向当前元素的上一
个元素 。
指针的运算
? 指针之间只支持减法运算 , 且必须参与运算的指针类型必
须相同
p1 – p2; ( (unsigned int)p1 - (unsigned int)p2) / sizeof(type);
注意 :
? 只有当两个指针指向同一个数组中的元素时 , 指针
相减才有意义 , 其意义为指针所指元素的下标差
? 当两个指针指向的元素不在同一个数组中时 , 结果
未定义
指针的比较
? 指针也可以进行关系运算
< <= > >=
? 指针关系运算的前提是同时指向同一个数组中的元素
? 任意两个指针之间的比较运算(==, !=) 无限制
数组的访问
? 以 下标的形式 访问数组中的元素
? 以 指针的形式 访问数组中的元素
标 下标 VS 指针
? 从理论上而言 , 当指针以固定增量在数组中移动时 , 其效
率高于下标产生的代码
? 当指针增量为1 且硬件具有硬件增量模型时 , 表现更佳
注意 :
现代编译器的生成代码优化率已大大提高 , 在固定增
量时 , 下标形式的效率已经和指针形式相当 ; 但从可
读性和代码维护的角度来看 , 下标形式更优 。
a 和&a 的区别
?a 为数组是数组首元素的地址
?&a 为整个数组的地址
?a 和&a 的意义不同其区别在于指针运算
a + 1 (unsigned int)a + sizeof(*a)
&a + 1 (unsigned int)(&a) + sizeof(*&a)
数组参数
?C 语言中 , 数组作为函数参数时 , 编译器将
其编译成对应的指针
void f(int a[]); ?? void f(int* a);
void f(int a[5]); ?? void f(int* a);
结论 :
一般情况下 , 当定义的函数中有数组参数时 , 需要定
义另一个参数来标示数组的大小 。
指针和数组的对比
? 数组声明时编译器自动分配一片连续内存空间
? 指针声明时只分配了用于容纳指针的4 字节空间
? 在作为函数参数时 , 数组参数和指针参数等价
? 数组名在多数情况可以看做常量指针 , 其值不能改变
? 指针的本质是变量 , 保存的值被看做内存中的地址
C 语言中的字符串
? 从概念上讲 ,C 语言中没有字符串数据类型
? 在C 语言中使用字符数组来模拟字符串
? C 语言中的字符串是以’\0’ 结束的字符数组
? C 语言中的字符串可以分配于栈空间 , 堆空间或者只读存储区
字符串长度
? 字符串的长度就是字符串所包含字符的个数
? C 语言中的字符串长度指的是第一个’\ 0 ’ 字符前出现的字符个数
? C 语言中通过’\0’ 结束符来确定字符串的长度
strlen 的返回值是用无符号数定义的 , 因此
相减不可能产生负数 , 以上的语句不等价 。
? 一般情况下 , 千万千万不要自行编写C 标准库已经提供的函数 。
? 标准库有时会使用汇编语言实现 , 目的就是为了充分利用机器所提供的特殊指令以追求最大的速度 。
? 复用已经存在的函数库会更高效 。
不受限制的字符串函数
? 不受限制的字符串函数是通过寻找字符串的结束符
’\0’ 来判断长度
字符串复制: :
char* strcpy(char* dst, const char* src);
字符串连接: :
char* strcat(char* dst, const char* src);
字符串比较 :
int strcmp(const char* s1, const char* s2);
? 不受限制的字符串函数都是以‘ \0 ’ 作为结尾标记来进
行的 , 因此输入参数中必须包含’ \0’ 。
? strcpy 和strcat 必须保证目标字符数组的剩余空间足
以保存整个源字符串 。
? strcmp 以0 值表示两个字符串相等
? 第一个字符串 大于 第二个字符串的时候返回值 大于0
? 第一个字符串 小于 第二个字符串的时候返回值 小于0
? strcmp 不会修改参数值 , 但依然以’\ \0 0’ 作为结束符
长度受限的字符串函数
? 长度受限的字符串函数接收一个显示的长度参数用于
限定操作的字符数
字符串复制: :
char* strncpy(char* dst, const char* src, size_t len);
字符串连接: :
char* strncat(char* dst, const char* src , size_t len);
字符串比较 :
int strncmp(const char* s1, const char*s2 , size_t len);
? strncpy 只复制len 个字符到目标字符串
? 当源字符串的长度小于len 时 , 剩余的空间以’\0’ 填充 。
? 当源字符串的长度大于len 时 , 只有len 个字符会被复制 , 且
它将不会以’\0’ 结束 。
? strncat 最多从源字符串中复制len 个字符到目标串中
? strncat 总是在结果字符串后面添加’\0’
? strncat 不会用’\0’ 填充目标串中的剩余空间
? strncmp 只比较len 个字符是否相等
指针数组和数组指针分析
array 代表数组首元素的地址 , 那么matrix 代表什么 ?
array 和&array 的地址值相同 , 但是意义不同 , 那么指向它们的
指针类型相同吗 ?
? C 语言中通过typedef 为数组类型重命名
typedef type(name)[size];
? 数组类型 :
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
? 数组定义 :
AINT5 iArray;
AFLOAT10 fArray;
数组指针
? 数组指针用于指向一个数组
? 数组名是数组首元素的起始地址 , 但并不是数组的起始地址
? 通过将取地址符& 作用于数组名可以得到数组的起始地址
? 可通过数组类型定义数组指针 : ArrayType* pointer;
? 也可以直接定义 :type (*pointer)[n];
? pointer 为数组指针变量名
? type 为指向的数组的类型
指针数组
? 指针数组是一个普通的数组
? 指针数组中每个元素为一个指针
? 指针数组的定义 :type* pArray[n];
? type* 为数组中每个元素的类型
? pArray 为数组名
? n 为数组大小
float* a[3]
float*
float*
float*
main 函数的参数
int main()
int main(int argc)
int main(int argc, char *argv[])
int main(int argc, char *argv[], char *env[])
argc – 命令行参数个数
argv – 命令行参数数组
env – 环境变量数组
? main 函数可以理解为操作系统调用的函数
? 在执行程序的时候可以向main 函数传递参数
小结
? 数组指针本质上是一个指针
? 数组指针指向的值是数组的地址
? 指针数组本质上是一个数组
? 指针数组中每个元素的类型是指针
多维数组和多维指针
指向指针的指针
? 指针变量在内存中会占用一定的空间
? 可以定义指针来保存指针变量的地址值
指向指针的指针
? 为什么需要指向指针的指针 ?
? 指针在本质上也是变量
? 对于指针也同样存在传值调用与传址调用
二维数组与二级指针
? 二维数组在内存中以一维的方式排布
? 二维数组中的第一维是一维数组
? 二维数组中的第二维才是具体的值
? 二维数组的数组名可看做常量指针
int a[3][3]
a[0]
a[1]
a[2]
数组名
? 一维数组名代表数组首元素的地址
int a[5] ? a 的类型为int*
? 二维数组名同样代表数组首元素的地址
int m[2][5] ? m 的类型为int(*)[5]
结论 :
. 1. 二维数组名可以看做是指向数组的常量指针
. 2. 二维数组可以看做是一维数组
. 3. 二维数组中的每个元素都是同类型的一维数组
如何动态申请二维数组
以二维指针模拟
小结
C 语言中只有一维数组 , 而且数组大小必须在
编译期就作为常数确定
C 语言中的数组元素可是任何类型的数据 , 即
数组的元素可以是另一个数组
C 语言中只有数组的大小和数组首元素的地址
是编译器直接确定的
数组参数和指针参数分析
退化的意义
C 语言中只会以值拷贝的方式传递参数
? 当向函数传递数组时
? 将整个数组拷贝一份传入函数
? 将数组名看做常量指针传数组首元素地址
C 语言以高效为最初设计目标 , 在函数传递的时
候如果拷贝整个数组执行效率将大大下降 。
二维数组参数
? 二维数组参数同样存在退化的问题
? 二维数组可以看做是一维数组
? 二维数组中的每个元素是一维数组
? 二维数组参数中第一维的参数可以省略
void f(int a[5]); ?? void f(int a[]); ?? void f(int* a);
void g(int a[3][3]); ?? void g(int a[][3]); ?? void g(int (*a)[3]);
等价关系
数组的指针 :char (*a)[4] 二维数组 :char a[3][4]
指针的指针 :int** a 指针数组 :int* a[5]
指针 :float* a 一维数组 :float a[5]
等效的指针参数 数组参数
注意事项
C 语言中无法向一个函数传递任意的多维数组
为了提供正确的指针运算 , 必须提供除第一维之外的
所有维长度限制
一维数组参数 – 必须提供一个标示数组结束位置的长度信息
二维数组参数 – 不能直接传递给函数
三维或更多维数组参数 – 无法使用
函数与指针分析
函数类型
? C C 语言中的函数有自己特定的类型
? 函数的类型由 返回值 , 参数类型 和 参数个数 共同决定
例 :int add(int i, int j) 的类型为int(int, int)
? C 语言中通过typedef 为函数类型重命名
typedef type name(parameter list)
? 例 :
typedef int f(int, int);
typedef void p(int);
函数指针
? 函数指针用于指向一个函数
? 函数名是执行函数体的入口地址
? 可通过函数类型定义函数指针 : FuncType* pointer;
? 也可以直接定义 :type (*pointer)(parameter list);
? pointer 为函数指针变量名
? type 为指向函数的返回值类型
? parameter list 为指向函数的参数类型列表
回调函数
? 回调函数是利用函数指针实现的一种调用机制
? 回调机制原理
? 调用者不知道具体事件发生的时候需要调用的具体函数
? 被调函数不知道何时被调用 , 只知道被调用后需要完成的任务
? 当具体事件发生时 , 调用者通过函数指针调用具体函数
? 回调机制的将调用者和被调函数分开 , 两者互不依赖
指针阅读技巧解析
? 右左法则
1. 从最里层的圆括号中未定义的标示符看起
2. 首先往右看 , 再往左看
3. 当遇到圆括号或者方括号时可以确定部分类型 ,
并调转方向
4. 重复2,3 步骤 , 直到阅读结束
#include <stdio.h>
int main()
{
int (*p2)(int*, int (*f)(int*));
int (*p3[5])(int*);
int (*(*p4)[5])(int*);
int (*(*p5)(int*))[5];
}
动态内存分配
为什么使用动态内存分配
C 语言中的一切操作都是基于内存的
变量和数组都是内存的别名 , 如何分配这些内存由编
译器在编译期间决定
定义数组的时候必须指定数组长度
而数组长度是在编译期就必须决定的
malloc 和free
? malloc 和free 用于执行动态内存分配和释放
? malloc 所分配的是一块连续的内存 , 以字节为单位 ,
并且不带任何的类型信息
? free 用于将动态内存归还系统
void* malloc(size_t size);
void free(void* pointer);
注意 :
? malloc 实际分配的内存可能会比请求的稍微多一点 , 但是不
能依赖于编译器的这个行为
? 当请求的动态内存无法满足时malloc 返回NULL
? 当free 的参数为NULL 时 , 函数直接返回
calloc 和realloc
? 你认识malloc 的兄弟吗 ?
void* calloc(size_t num, size_t size);
void* realloc(void* pointer, size_t new_size);
? calloc 的参数代表所返回内存的类型信息
? calloc 会将返回的内存初始化为0
? realloc 用于修改一个原先已经分配的内存块大小
? 在使用realloc 之后应该使用其返回值
? 当pointer 的第一个参数为NULL 时 , 等价于malloc
小结
? 动态内存分配是 C 语言中的强大功能
? 程序能够在需要的时候有机会使用更多的内存
? malloc 单纯的从系统中申请固定字节大小的内存
? calloc 能以类型大小为单位申请内存并初始化为0
? realloc 用于重置内存大小
程序中的栈
? 栈是现代计算机程序里最为重要的概念之一
? 栈在程序中用于维护函数调用上下文 , 没有栈就没有
函数 , 没有局部变量
? 栈保存了一个函数调用所需的维护信息
? 函数参数 , 函数返回地址
? 局部变量
? 函数调用上下文
程序中的堆
? 为什么有了栈还需要堆 ?
? 栈上的数据在函数返回后就会被释放掉 , 无法传递到函数外
部 , 如 : 局部数组
? 堆是程序中一块巨大的内存空间 , 可由程序自由使用
? 堆中被程序申请使用的内存在程序主动释放前将一直有效
堆空间通过申请才能获得 !
? 系统对堆空间的管理方式
? 空闲链表法 , 位图法 , 对象池法等等
程序中的静态存储区
? 程序静态存储区随着程序的运行而分配空间 , 直到程
序运行结束
? 在程序的编译期静态存储区的大小就已经确定
? 程序的静态存储区主要用于保存程序中的全局变量和
静态变量
? 与栈和堆不同 , 静态存储区的信息最终会保存到可执
行程序中
小结
? 栈 , 堆和静态存储区是C语言程序常涉及的三个基本内存区
? 栈区主要用于函数调用的使用
? 堆区主要是用于内存的动态申请和归还
? 静态存储区用于保存全局变量和静态变量
野指针和内存操作分析
初识野指针
? 野指针通常是因为指针变量中保存的值不是一个合法的内存地址而造成的
? 野指针不是NULL 指针 , 是指向不可用内存的指针
? NULL 指针不容易用错 , 因为if 语句很好判断一个指针
是不是NULL
C 语言中没有任何手段可以判断一个指针是否为野指针 !
? 局部指针变量没有被初始化
? 使用已经释放过后的指针
? 指针所指向的变量在指针之前被销毁
非法内存操作分析
? 结构体成员指针未初始化
? 没有为结构体指针分配足够的内存
内存初始化分析
? 内存分配成功 , 但并未初始化
内存越界分析
? 数组越界
内存泄露分析
多次指针释放
使用已释放的指针
? 用malloc 申请了内存之后 , 应该立即检查指针值是否
为NULL , 防止使用值为NULL 的指针
? 牢记数组的长度 , 防止数组越界操作 , 考虑使用柔性
数组
? 动态申请操作必须和释放操作匹配 , 防止内存泄露和
多次释放
? free 指针之后必须立即赋值为NULL
认清函数的真面目
? 函数的由来
序 程序 = 据 数据 + 算法
C 序 程序 = 据 数据 + 函数
函数的意义
? 模块化程序设计
面向过程的程序设计
? 面向过程是一种以过程为中心的编程思想
? 首先将复杂的问题分解为一个个容易解决的问题
? 分解过后的问题可以按照步骤一步步完成
? 函数是面向过程在C 语言中的体现
? 解决问题的每个步骤可以用函数来实现
声明和定义
? 程序中的声明可理解为预先告诉编译器实体的存
在 , 如 : 变量 , 函数 , 等等
? 程序中的定义明确指示编译器实体的意义
声明和定义并不相同 !!!
函数参数
函数参数在本质上与局部变量相同 , 都是在栈上分配空间
? 函数参数的初始值是函数调用时的实参值
函数参数的求值顺序依赖于编译器的实现 !!
C 语言中大多数运算符对其操作数求值的顺
的 序都是依赖于编译器的实现的 !!!
int i = f() * g();
程序中的顺序点
? 程序中存在一定的顺序点
? 顺序点指的是执行过程中修改变量值的最晚时刻
? 在程序达到顺序点的时候 , 之前所做的一切操作必须反映
到后续的访问中
? 每个完整表达式结束时
? &&, ||, ?:, 以及逗号表达式的每个运算对象计算之后
? 函数调用中对所有实际参数的求值完成之后( 进入函数体之前)
函数的缺省认定
? C 语言会默认没有类型的函数参数为int
小结
? C 语言是一种面向过程的语言
? 函数可理解为解决问题的步骤
? 函数的实参并没有固定的计算次序
? 顺序点是C 语言中变量改变的最晚时机
? 函数定义时参数和返回值的缺省类型为int
函数递归与函数设计技巧
递归概述
? 递归是数学领域中概念在程序设计中的应用
? 递归是一种强有力的程序设计方法
? 递归的本质为函数内部在适当的时候调用自身
递归函数
?C 递归函数有两个主要的组成部分 :
? 点 递归点 – 以不同参数调用自身
? 出口– 不在递归调用
f(x) =
1; x = 1
x * f(x-1); x > 1
小结
? C 语言中的递归函数必然会使用判断语句
? 递归函数在需要编写的时候定义函数的出口 , 否则
栈会溢出
? 递归函数是一种分而治之的思想
函数设计技巧
? 不要在函数中使用全局变量 , 尽量让函数从意义上是一个
独立的功能模块
? 参数名要能够体现参数的意义
void str_copy (char *str1, char *str2);
void str_copy (char *str_dest, char *str_src);
函数设计技巧
? 如果参数是指针 , 且仅作输入参数用 , 则应在类型前加
const , 以防止该指针在函数体内被意外修改
void str_copy (char *str_dest, const char *str_src);
函数设计技巧
? 不要省略返回值的类型 , 如果函数没有返回值 , 那么
应声明为void 类型
? 在函数体的“ 入口处” , 对参数的有效性进行检查 , 对
指针的检查尤为重要
? 语句不可返回指向“ 栈内存” 的“ 指针” , 因为该内存在
函数体结束时被自动销毁
函数设计技巧
? 函数体的规模要小 , 尽量控制在80 行代码之内
? 相同的输入应当产生相同的输出 , 尽量避免函数带有“ 记
忆” 功能
? 避免函数有太多的参数 , 参数个数尽量控制在4 个以内
函数设计技巧
? 有时候函数不需要返回值 , 但为了增加灵活性 , 如支持链式表达 , 可
以附加返回值
char s[64];
int len = strlen(strcpy(s, “android”));
函数名与返回值类型在语义上不可冲突
char c;
c = getchar();
if(EOF == c)
{
//…
}
C语言02基础深入理解(二)
标签:
原文地址:http://blog.csdn.net/opera95/article/details/51198909