9.1 指针概念
预备知识
1 内存
总线:地址线(64M=26根) 数据线(32根) 控制线(读写)
地址:最小单位(0地址-0字节,1地址-1字节) 对齐(指令是按4字节对齐的)
2 指令
访存指令: 可以读写内存,用2个寄存器,一个存地址,一个放数据
LDR r0, [r1] // mem(r1) -> data(r0)
STR r0, [r1] // data(r0) -> mem(r1)
怎么区分内存里面的数据和指令?
PC 程序计数器,一般初值为0,指向第一条指令
除非碰到跳转指令,否则通常情况下,pc = pc + 4
3 变量
变量的本质是什么? 变量是C语言出现之后才有的
2个属性:变量名 int a = 100; 变量值 a + 2;
变量的本质是:在C语言中,由C编译器分配的,表示内存地址和存储单元内容的一种联系。
4 指针变量
指针的本质是什么? 指针也是一种变量,但这个变量值它就是内存地址。
0xbfxxxxxx 函数栈内,局部变量
0x8048xxx 数据段,全局变量
9.2 指针用法
1 指针变量定义:
int * p;
2 指针变量赋值:
p = &a; // good
p = 0x8048xxx; // no error, not good.
补充: int * p = &a; 应该看待为 int* p; 同时 p = &a; 而不是 p = &a;
3 指针变量使用:
*p = 200; // 赋值
printf("%d", *p); // 读内容
p--; // 指针调整
4 指针变量类型:
char * p;
short * p;
int * p;
指针变量的类型,决定了用 *p 去访问内存时候的读取的字节数
5 易混淆的错误
1) &a 的用法
&a 是一个常量(由编译器决定),代表内存地址
&a 是一个32位整型,与a是什么类型无关
&a = 100; 试图修改a的地址,这样的用法是错误的
2) p 的用法
int * p;
*p = 1; // wrong!
p = 1; // no error
给指针本身赋值p=1是允许,但如果用指针取内容 *p=1 这是有风险的,会出段错误。
6 指针的重要用法-传参
用于获取用户输入
int a;
scanf("%d", &a);
int * p = &a;
scanf("%d", p);
用于交换 swap 函数
void swap(int * a, int * b);
// 可以用于修改上一级主调函数内部的变量的值
也可以通过传指针来获取多于1个以上的返回值
void decompose(double x, long * int_part, double * frac_part);
课堂练习: 请使用 char * 指针,对一个整型数 a = 0x12345678 修改为大端存储 提示: void swap(char * p1, char * p2);
7 指针的重要用法-const保护
int main(int argc, const char * argv[]);
int printf(const char * format, ...);
int scanf(const char * format, ...);
char * strcpy(char * dst, const char * src);
const int * p; // const 修饰 *p
*p = 100; // error
*(p+1) = 100; // next pos is error too
a = *p; // ok
p++; // ok
对比学习 const 的另一种用法
int * const p; // const 修饰 p
*p = 100; // ok
a = *p; // ok
p++; // error
8 指针的重要用法-用作返回值
用传入的指针作为函数返回的指针,这是允许的。
重要结论: 如果是函数内部局部变量的地址作为指针返回,这是有风险的。(编译没错,但不好)
9.3 指针和数组
1 指针的算术运算
1) 加一个整数 p+1 (地址的增加值取决于指针的类型,整型指针则加4)
2) 减一个整数 p-1 (地址的减少值取决于指针的类型,整型指针则减4)
3) 两个指针相减 p1-p2 (结果是两个指针之间的元素个数,而不是字节数)
只有相同类型的指针才能作减法,不同类型不能相减,编译器会报error
4) 自增加 p++, ++p
5) 自减少 p--, --p
6) 指针的比较 (p1 > p2)
2 特殊类型指针 void * p
void * p = &a; // ok
p = 100; // ok
a = *p; // error
*p = b; // error
p+1 (整数加1) // ok
p++/++p (加1) // ok
void * malloc(size_t);
3 指针名和数组名
int a[10];
int * p = a;
有何异同?
a[0] vs p[0] => 相同的
*p vs *a => 相同的
*(p+1) vs *(a+1) => 相同的
p+1 vs a+1 => 相同的
p++ vs a++ => 不同的(数组名是一个常量,不能修改)
4 指针和二维数组
int a[5][6];
int * p = &a[0][0];
int * p = a;
int * p = a[0];
int * p = a[2];
课堂作业: 请用指针实现对一个数组的调整,要求奇数在左边,偶数在右边。 要求: 尽可能不占用额外的存储空间。
编程方法
最重要的就是分解(设计好的函数),更重要的,在于设计程序时,从何种角度考虑解法?
判定标准:
1) 逻辑简单 (嵌套少一些)
能用1个while,就不用2个
能不用else,就不用
while, if, for 之间的递进逻辑少
代码行数来作为参考
2) 学会设计函数
原则上,能够复用的函数是好的设计,能多次复用最好
函数的功能最好少一些,一个函数只完成一个单一功能
函数内部除了调试语句打印外,最好不打印
3) 数据驱动编程
《Unix 编程艺术》 -> Data Drive
精心考虑所谓算法处理过程中要发生变化的数据是什么,以及何种条件下变化?
5 动态内存分配 allocate
堆heap 和 栈stack 的概念
&a = 0xbf9cb86c 栈空间 3G 向下
p = 0x8129008 堆空间 向上
&b = 0x804a018 数据段(全局变量)跟着代码段
main = 0x8048414 代码段最小
常用函数
void * malloc(size_t size);
void free(void * ptr);
void * calloc(size_t nmemb, size_t size);
void realloc(void * ptr, size_t size);
void * alloca(size_t size);