机器中有一些位置,每一个位置被称为【字节】/byte,许多现代机器上,每个字节包含8个位。更大内存单位【字】,通常包含2个或4个字节组成。
他仍然只有一个地址,是最左边还是最右边的那个字节的位置,取决于机器。
机器事实-关于整型的起始位置:
在要求边界对齐(boundaryalignment)的机器上,整型存储的起始位置只能是某些特定的字节,通常是2或4的倍数。
所有高级语言的特性之一,就是通过名字而不是地址来访问内存的位置。那么名字和地址是怎么关联的呢?关联关系不是硬件所提供的,它是由编译器实现的,硬件仍然通过地址访问内存位置。
什么类型取决于使用方式,如果使用整型算术指令,这个值就被解释为整数,如果使用浮点型指令,他就是个浮点数。
存储是一个值,这个值,通常被当做地址来使用。
编译器为每个变量选择存储位置。
原因应该是这样设计灵活,且效率高,更重要的是硬件的设计如:RAM 有地址这个概念
用来执行间接访问操作。对一个变量间接访问,声明含义?就是不把这块内存值直接使用,二十把它用作地址,把这个地址对应的内容取出。
这个操作可能会有下面情景发生
1. 运气好 a 的初始值是非法地址,出错,终止程序,在unix系统上这个错误memory fault ,在 windows出现 一般保护性异常 (general protection exception)
2.更严重的情况,这个指针偶尔可能包含一个合法地址,即是这样的情况: 引发错误的代码,与原先用于操作那个值得代码完全不相干。
表示不指向任何东西。定义一个变量赋值为零。与零值比较来判断指针是不是NULL指针。
NULL 指针可能不是0,在此情况,编译器将负责零值和内部值之间的翻译和转换。
因编译器而异,有的不报错。有的发生错误,并终止程序。
如果你已经知道指针将被初始化为什么地址,就把它初始化为该地址,否则把它初始化为NULL。
校验,使用变量类型和应该使用的变量的类型不一致时,编译器会报错。
分析下 *&a = 25; 非重点
*100 = 25; *100 就是指针常量。这条语句是非法的,需要强制类型转换如:
*(int *) 100 = 25;
如:与输入输出设备控制器通信。
Int a =12;
Int *b = &a;
Int **c =&b;
画图要点:
1.箭头===》 是地址
2. 虚线。表示关系
3. 实线。 表示*访问
粗椭圆 当做右值使用,使用表达式的值。
粗方框 当做左值使用,指地址。
&ch结果会存储在某个地方/某个内存,但是我无法知道他位于何处,这个表达式,看不出他在机器中那个特定位置,所以它不是一个合法的左值。
左值可以再“=”号左侧,也可以在等号右侧,而右值只能出现在等号右侧。
Char ch = ‘a’;
Char *cp = &ch;
示意图要求1. 标出求值的顺序,2中间专题是虚线,椭圆,方框
表达式
&ch
Cp
&cp
*cp
*cp+1
*(cp+1)
++cp
Cp++
*++cp
*cp++
++*cp
(*cp)++
++*++cp
++*cp++
实例,strlen.c 必会默写
只能用于指向数组中某个元素的指针,也适用于使用malloc函数动态分配获得的内存。
指针指向数组最后一个元素的后面的那个内存位置,有什么问题?允许这样,一般不允许对指向这个位置的指针进行间接访问。
只有两个指针指向同一个数组中的元素时,相减的结果是 ptrdiff_t(有符号整数类型)
他们都指向同一个数组中的元素,但是两个任意的指针可以执行相等或不相等测试。
答: 数组和指针有着本质的不同,指针是一个标量值。数组名却有很多属性如:数组元素的数量。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。
只有两种场合下,数组名并不用指针常量表示 1. Sizeof 操作符
2. Int a[10]; int *c; c = &a[0]; ???
有两种方式如: intb[10]; 1. b[3] ; 2. *(b+3)
即是: 1. Array[subscript]; 2.*(array+ (subscript));
Int array[10];
Int *ap = array+2;
ap ; *ap ; ap[0]; ap+6; *ap+6; *(ap+6); ap[6] ; &ap ; ap[-1]; ap[9];
2[array];
下标引用可以作用于任意的指针,而不仅仅是数组名。 耗时麻烦
下标绝不会比指针更有效率,但指针有时会比下标更有效率。
这个例子完成的功能是将数组中的所有元素都设置为0;
。。。。
查看代码产生的汇编代码,不断调整指针和下标的使用方式,来比较不同的汇编代码
大多数情况下,不要仅仅为了几十微妙的执行时间,而写一些莫名其妙的代码。
它是传值方式,传递的是该指针的拷贝。
举个例子。。。。
Int strlen(char *string);
Int strlen(char string[]);
形参只是一个指针。
大括号: int vector[5] = {10,20,30,40,50}
数组初始化的方式类似于标量变量的初始化方式,,也即是取决于他们的存储类型,分为静态和动态初始化。
不完整的初始化也是可以的
如:in vector[5] ;
自动计算数组长度。
Int vector[] = {1,2,3}
Char message[] = {‘H’,’e’,’l’,’l’,’o’,0};
也可以: charmessage[] = “Hello”;
字符数组和字符串常量在声明定义上有什么不同?
Char message1[] = “Hello”;
Char *message2 = “Hello”;
图呢? 画下呀todo;
定义一个数组,里边都是字符常量,该怎么理解呢?
如: char const keyword[] = {
“do”,
“for”,
“if”,
“register”,
“switch”,
“while”
}
画个图吧这样好理解 todo; 这个是指针数组
Storage order ,在c中多维数组的元素存储顺序按照最右边下表率先变化的原则。
这个顺序是标准定义的。
举个例子让事实来印证这个结论。
Int matrix[6][10];
Int *mp;
...
Mp = &matrix[3][8];
Printf(“First value is %d\n”,*mp);
Printf(“Second value is %d\n”,*++mp);
Printf(“Third value is %d\n”,*++mp);
Matrix 是一个指向一个包含10个整型元素的数组的 指针。
*(matrix+1) +5
*(*(matrix +1) +5)
这里有个概念 叫: 指向数组的指针 。如 matrix叫指向数组的指针。
Int vector[10] ,*vp = vector;
Int matrix[3][10];
Int *mp = matrix; //xxx这种初始化方式或声明是不对的。他的本意是声明一个指向数组的指针。
Int (*p)[10] = matrix; //这是正确的,声明一个指向数组的指针。
这样的指针是逐行在数组中移动。
我想逐个方位整型元素该怎么声明?
Int *pi = &matrix[0][0];
Int *pi = matrix[0];
错误的声明如:int (*p)[] = matrix;
先来说说一个数组的函数原型可以怎么写吧。
Int vector[10];
.....
Funcl(vector);
像下面两种都是可以的
Void funcl(int *vec);
或 Voidfuncl(int vec[]);
再来说下多维数组作为参数的情况
Int matrix[3][10];
......
Func2(matrix);
可以使用下面两种方式声明
Void func2(int (*mat)[10]);
Void func2(int mat[][10]);
结论要点:编译器必须知道第二个以及以后各维的长度才能对各下标进行求值,
切记不对的方式如下:
Void func2(int **mat); //这个是指向整型指针的指针,他和指向整型数组的指针不是一回事。
多维数组怎么初始化呢?
有两种形式 1.:
Int matrix[2][3] = {100, 101 ,102, 110, 111,112};
2:
Matrix[0][0] = 100;
Matrix[0][1] = 101;
Matrix[0][2] = 102;
Matrix[1][0] = 110;
Matrix[1][1] = 111;
Matrix[1][2] = 112;
多维数组中使用多个大括号层级分明的声明比较好
Int three_dim[2][3][5] = {
{
{000,001, 002, 003 ,004},
{ 010, 011, 012, 013, 014},
{ 020, 021, 022, 023, 024}
},
{
{ 100, 101, 102, 103, 104},
{ 110, 111, 112, 113, 114},
{120, 121, 122, 123, 124}
}
}
花括号有什么好处呢?
1. 起到路标的作用
2,不完整的初始化列表,很有用
多维数组为什么只有第一维可以缺省呢?如: int two_dim[][5] = { {},{},{}}
原则上编译器可以让其他维缺省,但是需要每个列表中的子初始值至少有一个整出现。最终标准这种了,要求其他维大小显示提供,好处是所有的初始值列表都无需完整呀,这个好处更有用,哈哈。
指针数组怎么声明呢?
Int *api[10];
什么情况下会用到指针数组呢?
写个例子,判断参数是否与一个关键字列表中的任何单词匹配,
并返回匹配的索引值,如果未找到匹配,函数返回-1
Char const keyword[] = {
“do”,
“for”,
“register”
};
也可以把关键字存储在一个矩阵中,
Char const keyword[][9] = {
“do”,
“for”,
“register”
}
好的习惯,通常选择指针数组,注意末尾NULL
Char const *keyword[] = {
“do”,
“for”,
“if”,
NULL
}
For( kwp = keyword_table ; *kwp !=NULL; kwp++)
总结 :p166
复杂的表达式样的声明怎么理解?
C 语言 设计为 推论声明。用于声明的表达式和普通的表达式再求值时,所使用的规则相同。
分析几个复杂的声明?
Int *f(); //返回值,是一个指向整型的指针
Int (*f)(); //f 函数指针 ,,,指向一个函数。
Int *(*f)();
Int (*f[])( int, float);
Int *(*f[])( int , float);
函数指针是合法的吗?怎么声明?
Int (*f)();
Int f()[];
这个是非法的,他的返回值是一个整型数组。注意!函数
只能返回标量值,不能返回数组。
Int f[](); //是一个数组, 他的元素是函数,这个函数他的返回值是整型。
这个声明是非法的,因为数组元素必须具有相同的长度,但不同的函数显然可能具有不同的长度。
int f(int);
Int (*pf)(int) = &f; 或者 int (*pf)(int ) = f;
注意在函数指针的初始化之前具有f的原型很重要,否则编译器无法检查f的类型是否与pf所指向类型一致。
注意,初始化的表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转化为函数指针。!!!
Int ans;
Ans =f(25); //函数名f首先被转换为一个函数指针。即是隐式间接访问
Ans = (*pf)(25);
Ans =pf(25); //画画图 todo;
1.吧函数指针作为参数传递给函数。
与函数指针,,相关的一个概念, 回调函数。
例子: 在一个单链表中查找一个指定值? 两个维度来分析这个例子(回调函数,函数指针)
查的程序怎么写的,有对比才知道好!!
他的参数是一个指向链表第一个节点的指针,一个指向我们需要查找值的指针,和一个函数指针,它指向的函数用于比较存储于链表中的类型的值。
Search.c 类型无关的链表查找
#include <stdio.h>
#include “node.h”
Node * search_list(Node * node, void const *value,
Int( *compare)( void const *, void const *))
{
While(node != NULL){
If(compare (&node->value,value) == 0 )
Break;
Node = node->link;
}
Return node;
}
Int compare_ints( void const *a ,void const *b)
{
If( *(int*)a == *(int *)b)
Return 0;
Else
Return 1;
}
如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者 执行只能由函数调用者定义的工作。可以使用这个技巧。
2. 转移表
举个计算器例子? 查的方法就是 switch case......
Double add (double, double) ;
Double sub (double, double) ;
Double mul (double, double) ;
Double div (double, double) ;
.......
Double (*oper_func[])(double ,double ) = {
Add,sub,mul,div,...
}
调用: result = oper_func[oper](op1,op2);
注意下标要处于合法范围!!
命令行参数是一个应用
例子cmd_line.c 要能熟练写出。
如 “xyz” 他的表达式是个指针常量, 指向第一个字符,也可以进行下标引用,间接访问,及指针运算。
*”xyz”
“xyz”[2]
13.4 神秘函数不太理解???
课后练习也是很有必要做的。
原文地址:http://liangge.blog.51cto.com/7154562/1896034