指针
内存和地址怎么理解呢?
机器中有一些位置,每一个位置被称为【字节】/byte,许多现代机器上,每个字节包含8个位。更大内存单位【字】,通常包含2个或4个字节组成。
一个字包含4个字节,它的地址是什么?
他仍然只有一个地址,是最左边还是最右边的那个字节的位置,取决于机器。
机器事实-关于整型的起始位置:
在要求边界对齐(boundary alignment)的机器上,整型存储的起始位置只能是某些特定的字节,通常是2或4的倍数。
变量名和地址关系?
所有高级语言的特性之一,就是通过名字而不是地址来访问内存的位置。那么名字和地址是怎么关联的呢?关联关系不是硬件所提供的,它是由编译器实现的,硬件仍然通过地址访问内存位置。
存储在内存的一个32位值,到底是整型还是浮点呢?
什么类型取决于使用方式,如果使用整型算术指令,这个值就被解释为整数,如果使用浮点型指令,他就是个浮点数。
指针变量他的地址对应的内存中存的是什么内容?
存储是一个值,这个值,通常被当做地址来使用。
对于int a=112; int *d = &a; 为什么编译器最终将a的地址写入d对应的内存空间,而不是把112写入d的内存空间呢?
原因应该是这样设计灵活,且效率高,更重要的是硬件的设计如:RAM 有地址这个概念
符号* 什么含义?
用来执行间接访问操作。对一个变量间接访问,声明含义?就是不把这块内存值直接使用,而是把它用作地址,把这个地址对应的内容取出。
注意 在 指针声明中的 * 和 表达式中的 * (包括 左值 和 右值 ) 的不同;
int main()
{
int a = 10;
int *p = &a;
int d = *p;
*p = 12;
printf("a=%d\n",a);
printf("d=%d\n",d);
return 0;
}
a=12
d=10
int *p = &a 代码 p 是个变量 类型 是 int * ; 这个类型是指针 指向int
int d = *p // *p 作为右值 代表间接访问操作 , 取出 p 中的 值(是地址),不算完 ,找到这个地址 的内存空间 取出里边 的值。
*p = 12 // *p 作为左值 代表间接访问操作,不是直接打开p 的内存空间 ,而是取出p 中 的 值 (是地址),找到这个地址 的内存空间,打开
指针未初始化是什么含义,有什么风险?对未初始化的指针访问会发生什么?如int *a ; *a=12;
这个操作可能会有下面情景发生
1. 运气好a 的初始值是非法地址,出错,终止程序,在unix系统上这个错误memory fault ,在windows出现一般保护性异常(general protection exception)
2.更严重的情况,这个指针偶尔可能包含一个合法地址,即是这样的情况:引发错误的代码,与原先用于操作那个值得代码完全不相干。
编译器能检测出来初始化的指针p, *p = 12,这样,并给出警告吗?
标准定义的NULL指针什么含义?我怎么定义一个NULL指针变量?怎么判断一个指针是不是NULL指针。
表示不指向任何东西。定义一个变量赋值为零。与零值比较来判断指针是不是NULL指针。
在机器层面NULL指针内容到底是什么呢?
NULL 指针可能不是0,在此情况,编译器将负责零值和内部值之间的翻译和转换。
对一个NULL指针间接访问会出现什么情况?
因编译器而异,有的不报错。有的发生错误,并终止程序。
怎么定义一个指针变量才是好习惯?
如果你已经知道指针将被初始化为什么地址,就把它初始化为该地址,否则把它初始化为NULL。
在对一个变量赋值时,编译器会校验,检查什么?
校验,使用变量类型和应该使用的变量的类型不一致时,编译器会报错。
分析下*&a = 25; 非重点
指针常量是什么能不能访问操作并赋值?
*100 = 25; *100 就是指针常量。这条语句是非法的,需要强制类型转换如:
*(int *) 100 = 25;
指针常量什么样情况才能用到?
如:与输入输出设备控制器通信。
指针的指针怎么用示意图画明白?
定义(包括声明和初始化)的分析 ,(就比如 下面的 变量 c 的声明和定义):
如: int **c = &b //首先 c 是变量 ; 它的类型 是 int ** ; 变量 c 的 值 是 b的地址
左值使用 :如 **c = 10 //
【变量】 //表达式含义 就是 打开 变量 对应地址 的 内存空间
*【变量】 //首先 找到变量 对应地址的 内存空间 打开, 第二步 * ,把这个地址里的 内容(当作地址)去找的内容值对应的 内存地址 再 打开
**【变量】 //表示多次打开, 遇到 * 操作 就把 当前地址里的 内容 当左地址,再寻找内容值代表的 地址空间 打开
注意* 是右结合性
Int a =12;
Int *b = &a;
Int **c = &b;
**c = 10;
画图要点:
1.箭头===》是地址
2. 虚线。表示关系
3. 实线。表示*访问
粗椭圆 当做右值使用,使用表达式的值。
粗方框 当做左值使用,指地址。
#include <stdlib.h>
#include <stdio.h>
int
main(){
int a=12;
int *b=&a;
int **c= &b;
printf("%p %p %p \n",a,b,c);
printf("%d %d %d \n",sizeof(a),sizeof(b),sizeof(c));
// printf("%s %s %s \n",a,*b,*c);
printf("%d %d %d \n",a,*b,**c);
}
结果:
分析结果: 指针占用八个字节,地址占用八个字节,实际上用6个字节表示地址(0x开头)
例子 :指针要用对等的地址
#include <stdlib.h>
#include <stdio.h>
int
main(){
int a=12;
int *b= &a;
int **c= &a;
printf("%p %p \n",a,c);
printf("%d %g \n",a,*c);
printf("%d %d \n",sizeof(a),sizeof(c));
}
结果:编译器报错!
结果分析:指针要用对等的地址(或低一级指针赋值如&b, 或者同级别指针&d)来赋值,编译器会检验出不符合的情况
为什么&ch 不能用作左值?
&ch结果会存储在某个地方/某个内存,但是我无法知道他位于何处,这个表达式,看不出他在机器中那个特定位置,所以它不是一个合法的左值。
int main()
{
int a = 10;
//&a = 20; //不能这样写,报左值 必须是合法的
*&a = 20;
printf("a=%d\n", a);
}
个人觉得: c的语法决定的, &就是取地址, &ch ;整个就是 表示个地址 , 如果说这个表达式 还能代表 左值 那 * 干啥呢,不就管的太宽了吗,职责不单一 ,*&a 这样就没法合理解读了,
&a 不能做左值 语法上这样设计合理。
什么是左值,右值?
左值可以再“=”号左侧,也可以在等号右侧,而右值只能出现在等号右侧。
分析清楚一个指针表达式是左值还是右值有什么好处?
分析表达式?
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函数动态分配获得的内存。
指针指向数组最后一个元素的后面的那个内存位置,有什么问题?允许这样,一般不允许对指向这个位置的指针进行间接访问。
// 感觉是 分配了个数组, 数组的 元素是 pthread_t 这个地方很妙啊! 使用 free来释放
pool->pthreads = (pthread_t *)malloc(sizeof(pthread_t) * thread_num);
if(NULL == pool->pthreads)
{
printf("failed to malloc pthreads!\n");
break;
}
pool->queue_close = 0;
pool->pool_close = 0;
int i;
for(i = 0; i < pool->thread_num; i++)
{
pthread_create(&(pool->pthreads[1]), NULL, threadpool_function, (void *)pool);
}
指针-指针什么情况下允许这样?
只有两个指针指向同一个数组中的元素时,相减的结果是ptrdiff_t(有符号整数类型)
< <= > >= 可以比较的两个指针有什么限制?
他们都指向同一个数组中的元素,但是两个任意的指针可以执行相等或不相等测试。
数组
数组和指针的区别与联系?
数组和指针有着本质的不同,指针是一个标量值。数组名却有很多属性如:数组元素的数量。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。
先来搞明白,什么是声明,什么是定义?
c 语言中的对象有且只有一个定义,但它可以有多个extern 声明。这里的对象,只是跟链接器有关的“东西”比如函数和变量。
定义只能出现一个地方,声明可以出现多次,声明相当于普通的声明,它说明并非本身,而是描述其他地方创建的对象。定义相当于特殊的声明,它为对象分配内存
左值在编译时可知,左值标示存储结果的地方。右值直到运行时才知,如无特殊说明,右值表示的内容。
C语言中有“可修改的左值”,很奇怪!意味着还有不可可以修改的左值,这个奇怪的术语是为与数组名(是左值)区分。
数组和指针是如何访问的?
char a[9] = “abcdefgh”; ….. c= a[i];
编译器符号表具有一个地址9980
运行时步骤1: 取i的值,将它与9980相加
运行时步骤2: 取地址(9980+i)的内容
char *p ; ……… c= *p;
编译符号表有一个符号p, 它的地址为4624;
运行时步骤1: 取4624的内容,就是5081
运行时步骤2: 取地址5081 的内容
指定的访问要灵活得多,但需要增加一次额外的提取。
定义为指针,但以数组方式引用时会发生什么?
指针当然可以p[i] 用,
char *p = “abcdefgh”; c=p[i];
编译器符号表具有一个p, 地址为4624
运行步骤1 :取地址4624的内容,即5081,
运行步骤2: 取得i的值,并将它与5081相加
运行步骤3: 取得地址[ 5081 + i] 的内容
指定的访问要灵活得多,但需要增加一次额外的提取。(和数组a对比)
注意写代码时,声明与定义相匹配
指针 | 数组 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元素 |
相关的函数malloc(), free(). | 隐式分配和删除 |
通常指向匿名数据 | 自身即为数据名 |
间接访问 | 直接访问 |
定义指针时,编译器并不为指针指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。如:
char *p = “break”;
float *pip = 3.14; //错误!!!!
什么时候数组和指针相同?
数组声明,包含三种
- extern , 如 extern char a[] ; 不能改写成指针的形式
- 定义,如 char a[10]; 不能改写成指针的形式
- 函数的参数,如func(char a[]); 选择数组形式,或者指针形式
数组,在表达式中使用
如c = a[i]; 选择数组形式 或指针形式
规则1: 表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。
例外情况如: a 数组作为sizeof()的操作数;b 使用& 操作符取数组的地址;c数组是一个字符串,常量初始值。???
如:
int a[10], i=2;
a[i]; 相当于*(a+i); *(i+a);i[a]; 2[a];
规则2: 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针。
规则3: 下标总是与指针的偏移量相同
事实上,下标范围检查被认为并不值得加入C语言中。
指针总是有类型限制?p+2,p+i ; 是的,因为编译器要直到步长啊。
数组形参是如何被引用的?
func(char p[]) ; …. C = p[i];
func(char *p) ; …. C= p[i];
编译器符号表显示p可以取址,从堆栈指针SP偏移14个位置
运行时步骤1 :从SP偏移14个位置找到函数的活动记录,取出实参。
运行时步骤2 :取i 的值,并与5081 相加
运行时步骤3 :取出地址(5081+i)的内容。
图???
数组/指针实参的一般用法
func(int * turnip) { ….}
或
func(int turnip[]) { …. }
或
func(int turnip[200]{ … }
int my_int;
int *my_int_ptr;
int my_int_array[10];
调用时的实参 | 类型 | 通常目的 |
func(&my_int) | 一个整型数的地址 | 一个int参数的地址调用 |
func(my_int_ptr) | 指向整型数的指针 | 传递一个指针 |
func(my_int_array) | 整型数组 | 传递一个数组 |
func(&my_int_array[i]); | 一个整型数组某个元素的地址 | 传递数组的一部分 |
指针和数组区别联系总结
- 用a[i]这样的形式对数组进行访问总是被编译器“改写”或解释为*(a+1)这样的指针访问。
- (指针角度)指针始终是指针。它绝不可以改写成数组。你可以用下标形式访问指针,一般都是指针作为函数参数时,而且你知道实际传递给函数的是一个数组。
- (函数的参数)数组作为函数的参数,声明中,一个数组可以看作是一个指针。调用中,作为函数参数的数组实参始终会被编译器修改成为,指向数组第一个元素的指针。
- 在其他所有情况中,定义必须和声明匹配。如果定义了一个数组,在其他文件对它进行声明时也必须把它声明为数组,指针也是如此。
例子:
#include <stdio.h>
#include <stdlib.h>
char ga[] = "abcedfghijklm";
void my_array_func(char ca[]) //并不是 整个数组copy进来!
{
printf(" addr of array param= %#x \n",ca); //和下面的 pa一样 形参是 指针,操作上没啥区别
printf(" addr of (ca[0]) = %#x \n", &(ca[0]));
printf(" addr of (ca[1]) = %#x \n", &(ca[1]));
printf(" ++ca = %#x \n\n", ++ca);
}
void my_pointer_func(char *pa)
{
printf(" addr of ptr param = %#x \n",pa);
printf(" addr (pa[0]) = %#x \n", &(pa[0]));
printf(" addr (pa[1]) = %#x \n", &(pa[1]));
printf(" ++pa = %#x \n\n", ++pa);
}
int main(void)
{
printf(" addr of global array =%#x \n",&ga);
printf(" addr (ga[0]) = %#x \n", &(ga[0]));
printf(" addr (ga[1]) = %#x \n\n",&(ga[1]));
my_array_func(ga);
my_pointer_func(ga);
}
// [xingqiji@work78 lingSheng]$ ./a.out
// addr of global array =0x404038
// addr (ga[0]) = 0x404038
// addr (ga[1]) = 0x404039
// addr of array param= 0x404038
// addr of (ca[0]) = 0x404038
// addr of (ca[1]) = 0x404039
// ++ca = 0x404039
// addr of ptr param = 0x404038
// addr (pa[0]) = 0x404038
// addr (pa[1]) = 0x404039
// ++pa = 0x404039
一个一维数组我怎么才能访问他的元素; 都有哪几种方式?
有两种方式如:int b[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};
也可以:char message[] = “Hello”;
字符数组和字符串常量在声明定义上有什么不同?
Char message1[] = “Hello”;
Char *message2 = “Hello”;
图呢?画下呀todo;
定义一个数组,里边都是字符常量,该怎么理解呢?
如:char const *keyword[] = {
“do”,
“for”,
“if”,
“register”,
“switch”,
“while”
}
画个图吧这样好理解 todo; 这个是指针数组
只有字符串常量才可以初始化指针数组。指针数组不能由非字符串的类型直接初始化:
int *weights[] = { //不合法,无法成功编译
{1, 2,3,4,5},
{6,7},
{8,9,10}
};
可以如下:线创建几个单独的数组,然后用这些数组名来初始化原先的数组。
Int row_1[] = {1,2,3,45,-1}
Int row_2[] = {6,7,-1};
Int row_3[] = {8,9,10, -1};
Int *weight[] = {
Row_1,
Row_2,
Row_3
}
在锯齿状数组上使用指针
价值:在存储各行长度不一的表以及在一个函数调用中传递一个字符串数组
char *turnip[10];
char my_string[] = “your message here”;
/*共享字符串*/
turnip[i] = &my_string[0];
/*拷贝字符串---尽量不要这么做*/
turnip[j] = malloc(strlen(my_string) +1);
strcpy(turnip[j], my_string);
多维数组
尽管术语上称作“多维数组”,但c语言实际上只支持“数组的数组”
如何分解多维数组,多维数组的存储?
Int apricot[2][3][5];
Int (*p)[3][5] = apricot;
Int (*r)[5] = apricot[i];
Int *t = apricot[i][j]
Int u= apricot[i][j][k]
分析r++ 和l++ 他们所增加的步长是很不相同的。
例子多维(三维)数组做参数例子
My_function_1( int fruit[2][3][5]) { …. }
My_function_2(int fruit [][3][5] ){ ….}
My_function_3(int (*fruit)[3][5]) {….}
Int apricot[2][3][5];
My_function_1(apricot );
My_functon_2(apricot);
My_function_3(apricot);
Int (*p)[3][5] = apricot;
My_function_1(p);
My_functon_2(p);
My_function_3(p);
Int (*q)[2][3][5] = &apricot;
My_function_1(*q);
My_function_2(*q);
My_function_3(*q);
一个二维数组存储的顺序到底怎样?
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);
指向字符串的指针的数组
用于实现多维数组的指针数组有多种名字,如“Iliffe向量“”display” 或“dope向量”
声明如:char *pea[4];
初始化:
字符常量初始化或
for(j=0; j <=4; j++)
pea[j] = malloc(6);
或
malloc(row_size * colum_size * sizeof(char));
然后使用一个循环,用指针指向这块内存的各个区域。
它减少了维护性开销,但缺点是当处理完一个字符串时,无法单独将其释放
例:看见squash[i][j] ,你知道都可能是那些声明?
a. Int squash[23][12]; //int类型的二维数组
或
b. int *squash[23]; // 23个int 类型指针 的 Iliffe向量
int **squash; // int 类型的指针的指针
int (*squash)[12]; // 指向int 数组,长度12,的指针
这点类似,在函数内部无法分辨传递给函数的实参究竟是一个数组还是一个指针。
对a和b 取squash[4][6] 过程画图说明。
函数的参数---数组和指针
数组和指针参数是如何被编译器修改的
实参 | 所匹配的形式参数 | ||
数组的数组 | Char c[8][10] | Char (*) [10] | 指向数组的指针 |
指针数组(是数组它的元素是指针) | Char *c[15]; | Char **c | 指针的指针 |
数组 指针(行指针) | Char (*c)[64]; | Char (*)[64] | 不改变 |
指针的指针 | Char **C | Char **C | 不改变 |
说明:1. 第一种情况 实参传到 函数中 函数的形参 后 实参 的8 信息丢失,必要时,需要增加 额外的参数 表示数量。
2. 第二种情况 实参 copy 形参 后 ,实参的 个数15 丢失,必要时,需要增加额外的参数 表示数量
向函数传递一个一维数组
任何一维数组均可以作为函数的实参,形参被改写成指向数组第一个元素的指针,所以需要一个约定来提示数组的长度。一般有两个方法:
- 增加一个额外的参数,表示元素的数目(argc 就是起这个作用)
- 赋予数组最后一个元素一个特殊的值,提示它是数组的尾部(字符串结尾的\0 字符就是起这个作用)。
向函数传递一个二维数组
数组被改写为指向数组第一行的指针。现在需要两个约定,其中一个用于提示每行的结束,另一个用于提示所有行的结束。
在函数内部如何声明一个二维数组,c 语言没办法表达“这个数组的边界在不同的调用中可以变化”这个概念,因c编译器必须要知道数组的边界,才能为下标引用产生正确的代码。所以要放弃传递二维数组,如本来想传递array[x][y] 改为array[x+1];array[y]s
向函数传递一个多维数组
c语言中,没有办法向函数传递一个普通的多维数组,这是因为我们需要知道每一维的长度(除了最左边一维),以便为地址运算提供正确的单位长度。
这样就把实参限制为除最左边一维外所有维都必须与形参匹配的数组
invert_in_place (int a[][3][5]);
下面两种都可以:
int b[10][3][5]; invert_in_place(b);
int b[999][3][5]; invert_in_place(b);
下面无法通过编译
int fails1[10][5][5]; invert_in_place(fails1);
int fails2[999][3][6]; invert_in_place(fails2);
我想传递任意行(i)任意例(j) 数组给一个函数做参数,该怎么声明?怎么一步步接近理想的情况?
方法一:
my_function (int my_array[10][20]);
方法二:
my_function(int my_array[][20]);
my_function(int (* my_array)[20]);
方法三:
my_function( char ** my_array);
注意:只有把二维数组改为一个指向向量的指针数组的前提下才可以这样做。
方法四:
char_array[row_size * I +j] = …
对多维数组作为参数传递的支持缺乏是c语言的内在限制,这使得用c语言编写某些特定类型的程序非常困难(如数值分析算法)
再来说下多维数组作为参数的情况
Int matrix[3][10];
......
Func2(matrix);
可以使用下面两种方式声明
Void func2(int (*mat)[10]);
Void func2(int mat[][10]);
结论要点:编译器必须知道第二个以及以后各维的长度才能对各下标进行求值,
切记不对的方式如下:
Void func2(int **mat); //这个是指向整型指针的指针,他和指向整型数组的指针不是一回事。
为什么NULL指针会导致printf 函数崩溃?
Char *p = NULL;
Printf(“%s”, p);
“未定义行为“
使用指针创建和使用动态数组?
在ANSI c中数组是静态的- 数组的长度是在编译时便已确定不变。所以下面形式会报错
:
cons tint limit = 100;
char plum[limit]; //error:intergral constant expression expected
在GNU c 是可以的不报错,c++中也是合法的
ANSI c中前向艺术(priorart)如下可引入动态数组
direct-declarator [ constant-expression opt] 改为:
direct-declarator [ expression opt]
动态数组
#include <stdlib.h>
#include <stdio.h>
…
int size;
char *dynamic;
char input[10];
printf(“please enter size of array: “);
size = atoi(fgets(input, 7, stdin));
dynamic = (char *) malloc(size);
…
dynamic[0] = ‘a’;
dynamic[1] = ‘z’;
调整动态数组大小:
- 对表进行检查,看看它是否真的已满。
- 如果确实已满,使用realloc() 函数扩展表的长度。并进行检查,确保realloc()操作成功进行
- 在表中增加所需要的项目。
Int current_element = 0;
Int total_element = 128;
Char *dynamic = malloc(total_element);
Void add_element(char c){
If(current_element == total_element -1){
Total_element *= 2;
Dynamic = (char *) realloc (dynamic ,total_element);
If(dynamic == NULL) error(“cound’nt expand the table”);
}
current_element++;
dynamic[current_element] = c;
}
在实践中,不要把realloc()函数的返回值直接赋给字符指针。如果realloc() 函数失败,它会使该指针的值变成NULL ,这样就无法对现有的表进行访问。
但,这个技巧并不是在所有地方都应该使用理由如下;
- 当一个大型表格突然增长时,系统的运行速度可能会慢下来,而且这在什么时候是无法预测的。
- 重分配操作很可能把原先的整个内存块内容迁移到一个不同的位置,这样表格中元素的地址便不在有效。为避免麻烦应该使用下标而不是元素的地址。
- 所有的“增加“和”删除“操作都必须通过函数来进行,这样才能维护表的完整性。
- 如果表的项目数量减少,可能应该缩小表并释放多余的内存。这对程序运行速度有很大影响
- 当某个线程对表进行内存重新分配时,你可能想锁住表,保护表的访问,防止其他线程读取表。对于多线程代码,这种锁总是必要的。
- 数据结构动态增加的另一种方式是使用链表,但链表不能进行随机访问。
工程师所存在的问题是他们采取欺骗手段以获得结果。
数学家所所存在的问题是他们研究一些玩具性的问题以获得结果。
程序员检验员所存在的问题是他们在玩具性的问题上采取欺骗手段以获得结果
二维数组的名字从指针读法该怎么读呢,如int matrix[3][10];?
Matrix 是一个指向一个包含10个整型元素的数组的指针。
分析下:声明如:int matrix[3][10]; 像matrix[1][5] 这样的表达式对应的指针形式怎么写?
*(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;
多维数组怎么初始化呢?
有两种形式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++)
函数指针是合法的吗?怎么声明?
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;
}
什么情况下要使用回调函数?
如果你所编写的函数必须能够在不同的时刻执行不同类型的工作或者 执行只能由函数调用者定义的工作。可以使用这个技巧。
- 转移表
举个计算器例子? 查的方法就是 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]