各种指针分类总结 + 指针间的赋值关系
来源:互联网 发布:如何减腓肠肌 知乎 编辑:程序博客网 时间:2024/05/01 16:18
有参考学习其它的,参考学习的来源如下:
http://blog.csdn.net/porscheyin/article/details/3461670
http://blog.csdn.net/lllazy520/article/details/7329635
深入理解void类型、 指针与数组的"爱恨情仇"、使用指针时的”陷阱“、
指针的赋值
指针的地址在内存中存储是是以数值的方式存储的,但这就并不代表指针或者说是地址就没有类型,只能说是它存储的时候本质是以没有类型来存储的,因为实质上内存空间上的内容在存储的时候都是没有类型的,但它读取时是有类型的,所以将一个非0数值赋予一个指针的操作时错的,必须要将这个数组进行(指针类型)的强制转换后,变成一个有类型的地址后才可以进行指针的赋值,这样通过指针进行间接访问和赋值时才知道要操作多大的空间,不过0和NULL就不用了,因为它本身就被系统宏定义为void *类型了,而void *类型能自动转换为各种类型,但注意非0数字并不是void *,所以还要对非0数字进行地址类型的赋予,如下:
#define (void *)0 NULL即为系统为NULL定义的宏定义
int *p = 0; 正确
int *p = 2-2; 正确
int *p = 0x0000; 正确
int *p = (int *)0x0000; 正确
因为以上都为0;
而
int *p = 258; 错误 int *p = (int *)258; 正确
int *p = 0x11010110; 错误int *p = (int *)0x11010110; 正确
int *p = NULL; 正确
char *cp = “abcdefg”;正确
野指针赋为NULL
ANSI C定义了零指针常量(即 NULL)的概念:一个具有0值的整形常量表达式,或者0值表达式被强制转换为void *类型,则称为空指针常量,它可以用来初始化或赋给任何类型的指针(即NULL指针)。也就是说,我们可以将0、0L、'/0'、2–2、0*5以及(void*)0赋给一个任何类型的指针,此后这个指针就成为一个空指针,由系统保证空指针不指向任何对象或函数。ANSI C为此定义了一个宏NULL,用来表示空指针常量,但不能对NULL指针进行解引用(*p)——会报错!——如此就保证了不会对野指针进行操作,因为在对任何被赋值为NULL的指针进行解引用时一定会报错!!!——所以一定要将野指针赋值为NULL,以避免误用!。不过char *p = NULL;则if(p)、if(!p)、while(p)、while(!p)是可以的。效果和if(p == NULL)、if(p != NULL)、while(p == NULL)、while(p != NULL)是一样的!,因为NULL的值就是0
NULL ----- (void*)0
NUL ------ ' \0 ' (ASCII 码表中定义的就是NUL)
nul ------ 不存在,没有定义这个东西
void *型指针作为一种通用的指针,可以和其它任何类型的指针(函数指针除外)相互转化而不需要类型强制转换,但不能对void *指针进行解引用及下标操作(因为无类型大小,无法访问!);
//指向const对象的一般指针---是不允许的 !所以也====绝不存在指向const 变量的一般指针,因为不能将const 变量的地址赋给一般指针!!!!!
//指向常量的指针只能是指向字符串常量的指针,而不能是指向const 的指针。
//---------即:指向常量的一般指针存在,但只能是指向常量字符串的char *指针,而指向const常量的一般指针根本就不可能出现!
一般指针、
野指针、
NULL指针(以上有述)、
函数返回指针、
指针常量、(有的书上又称为常指针)--------不可改变指向
常量指针---------操作权限受限,仅能“读”的指针,不可对指向对象的内容进行写
指向常量的指针-------指针变量,指向可变,只是指向的是常量,不可使用指针对常量的内容进行改变
字符串常量(出现在表达式中,除了sizeof ,& ,给数组初始化的情况,其它的都可以看成是一个地址——第一个字符的地址)、
指向数组元素的指针、
数组名、
指向数组的指针(数组指针)、
(二维数组名 与 指向数组的指针 和 指向指针的指针不同 !!)、
malloc、
函数名、
函数指针、
函数指针数组、
各种指针间的赋值关系
====================================================================================================================================
(1)野指针 ----------- 要防止它出现 !
指向了不确定的内存区域的指针,通常对这种指针进行操作会使程序发生不可预知的错误。
防止出现野指针必须要做到三大点:-----原理:对野指针乱指的不确定空间被误用是无法察觉的,反而是对NULL进行解引用的话编译器会发出报错!
1、在声明指针时将其初始化为NULL或零指针常量。------ 防止其乱指,指向的不确定空间被误用了而无法察觉
2、在free一个指针后,一定要将这个指针设置为NULL或零指针常量,除非能保证后面对这个free的指针不再使用,但及时对一个未指向的指针赋值NULL是一个良好的习惯!。------在释放完指向的空间后,指针变成野指针,防止误用其乱指的空间而无法察觉
(1.5)函数返回指针---------- 要保证返回 的地址上的空间内容没有被释放,保证是值传递,只要空间内容还在,那么传递的这个地址就是 有效的。
3、函数返回指针时,只能返回静态局部变量的地址、或函数malloc分配的指针、或传递的形参指针,只要保证返回的这个指针所指向的空间没被(手动、自动)释放、这个指向的地址空间的内容值还有效就行。------ 函数返回的地址是存在的,但是当函数返回之后,函数内部的空间就会自动释放,返回的地址仍存在,但是地址上存储的空间内容却被隐形释放了,这就会导致我们在对返回的地址进行解引用时还误以为此地址上的空间内容还存在,对获得的内容其实已经是不确定的无效值了还不自知,而这个也是编译器无法察觉的。所以对函数返回指针时一定要严加注意,一定要保证返回的指针所指向的空间还是有效的。
(2)指针常量---------- 只是指向被限制了而已 !,在定义时要马上初始化
int a = 6;
int * const p1= &a;//指针常量在定义时要马上初始化
int *const p2;//错误!!!因为不可再重新赋值了,此时给它初始化的值是一个不确定的随机无效值!
只是这个指针被声明为常量了,但它指向的空间内容并没有指为常量,所以可以对其指向的空间内容做变化,不能变的只是此指针的指向,一但初始化了就不能在赋予其其它的指针了,所以说在定义时就一定要用我们想要的地址值去初始化它有多必要。
但是要注意的是:但数组名并不是一个指针常量 ,只是可以看做一个指针常量!!!------- (具体可看(5)数组名的解释)
(3)常量指针 ----------- 只是其对指向的空间的操作权限(修改内容的权限)被限制了而已(只能访问,不能修改) !
const int * p1;//这两种定义方式都可以(只要在 * 的前面就可以了) !
int const * p2;
常量指针可以指向任何地址,也可以对它进行重定向(其指针的值可以被重新赋值),但无法通过对常量指针的解引用(*p 或p[] )来对其指向的地址空间内容进行修改,只能有访问的权限。但其指向的空间内容本身是什么就是什么,不受常量指针的影响,该可改就可改(非const变量),该不可改就不可改(const变量) !
常量指针常用于函数间的指针参数传递,以防止在函数内部对指针指向的内容进行修改,用常量指针作为形参,用一般指针作为实参,可以起到函数内部对传递的空间内容只有访问权限,从而保护了传递的地址空间内容,如 int strcmp(const char * str1 , const char * str2) 就是如此用法,即完成了比较,防止了被修改的隐患!
另外,常会对字符串常量返回的指针误以为是常量指针,但根据指针间的赋值原则来看,由char * s = "abcd"; 可得“abcd"返回的指针只能是char * 型一般指针,也就是说字符串常量在表达式中返回的指针仅仅只是一个指向字符常量的一般字符指针,而不是什么常量指针!
(4)字符串常量
在静态存储区分配地址空间,并且会返回一个指向字符的地址;
当用在表达式中时,除了一下三种情况:sizeof(”abcd“),&"abcd"(返回的是第一个字符的地址,这里的“abcd”仅当做字符来解释) ,给字符数组赋值char a[] = "abcd")时,其它情况下字符串常量"abde.."都可以当做一个指针处理,且是一个指向字符(指向字符串常量第一个字符)的一般指针,无法通过此常量指针对字符串常量的内容进行修改,此常量指针指向字符串常量的第一个字符。
常会对字符串常量返回的指针误以为是常量指针,但常量指针的定义表示式是const char *p,而根据指针间的赋值原则来看,由char * s = "abcd"; 可得“abcd"返回的指针只能是char * 型一般指针,也就是说字符串常量在表达式中返回的指针仅仅只是一个指向字符的一般指针(其实就只是静态分配区中的一个地址而已),而不是什么常量指针,字符串常量返回的确实是一个指向常量的一般指针,但却是指向一个字符串常量的首字符的指针,而不是指向const常量的指针而不是常量指针。不过倒时可以说字符串常量返回的指针是一个“指向常量的指针常量”,其实它并不是什么指针,就是一个常量取的地址而已,然后赋值给一般指针 char * , 而char *却不是指针常量,它只是一个普通指针,还可以改变指向的。要记得指向常量的一般指针存在,但只能是指向常量字符串的char *指针,而指向const常量的一般指针根本就不可能出现!
即const char c = 'k'; char * p = &c ;不可能实现。
能实现的只能是: char c = 'k' ; char *p = &c ; 而这时的'k'不能算是什么常量,只是一个复制的关系,在静态区中‘k’这个常量还不存在。
char s[] = "abcde"; //只是将字符串"abcde"赋给数组s的地址空间上,而字符串常量”abcde“从始至终都不存在,这里的”abcde“只是作为单纯的一个字符串,与字符串常量没关系,更谈不上返回一个常量指针了;
char *p = "abcdef";
printf(”%c“,*p); // 'a'
printf(”%c“,p[3]); // 'd'
p[2] = 'k';// 不允许!!!因为指向的内容是一个常量,虽然p是一个一般指针,对指向的空间内容有访问和修改操作权限,但这操作是否能够实现还要看这 个指向的空间内容自己本身的属性(看是否能够被修改),但当这个指向的空间内容本身是一个常量的时候,这时试图通过指针来修改 这个空间内容就是非法的操作了
printf("%d",sizeof("abcd")); //5
printf("%d",strlen("abcd")); // 4
printf("%p%p","abcd",&"abcd");//地址相等,都是字符串常量的第一个字符的地址
printf("%c",*"abcd"); // 'a'
printf("%c",*("abcd"+3)); // 'd'
printf("%c","abcd"[3]); // 'd'
”abcd“[2] = 'k';//不允许!!!因为指向的是一个常量字符,而且"abcd"返回的还是一个常量指针,怎么样都不允许这个改变内容值的操作!
当程序中的多处都出现同一相等的字符串常量"abcdef"时,因为字符串常量本身不可修改,只能起到访问的作用,所以即使是在多处出现同样的字符串常量,但在整个程序中的静态存储区中只会存在一份字符串常量,即多处出现的相等的字符串常量返回的常量地址都相等,等于第一个出现时编译器为其分配的地址。
顺便附加:
程序占用的内存5个区---------用户空间的内存分布7个段
静态存储区其实就是数据段data 和 bss 段 , 常量区其实就是text 段
程序占用的内存分类(下图):
从左上图可知,程序占用的内存被分了以下几部分.
所谓的文字常量区和程序代码区都放在进程空间的text代码段中,所以不能对字符串常量进行修改,因为text不允许被修改。
常量不一定就只是存储在常量区(text)中,也可存在于栈中,如const常量;
而变量也不一定就都存储于栈(局部变量和函数形参)中,也可存在于静态存储区(全局变量和static变量----data和bss)中,如static变量;
而堆区即不用来存储变量也不来存储常量,而是专门用于人工、手动、动态开辟内存malloc用的 !
(5)数组名----------很重要,看完这些全部,就能明白很多本质上的东西,对一些规则和叫法也就更加理解是为什么
数组一般有三种:全局/静态范围的数组,局部变量数组,申请堆空间来创建数组。
其中,全局数组和局部静态变量数组、局部动态变量都属于静态数组,前两个为静态区空间,最末一个在栈空间。
而申请堆空间来创建数组的属于动态数组。
{ 输出结果:
int a[10] = {0,1,2,3,4,5,6,7,8,9}; i[a] = 0i[p] = 0
int *p = a; i[a] = 1i[p] = 1
int i; i[a] = 2i[p] = 2
5[a] = 55; i[a] =33i[p] =33
3[p] = 33; i[a] = 4i[p] = 4
for(i = 0;i<10;i++) i[a] = 55 i[p] = 55
printf("i[a] = %di[p] = %d\n",i[a],i[p]); i[a] = 6i[p] = 6
return 0; i[a] = 7i[p] = 7
} // 输出结果为:如右所示 ——> i[a] = 8i[p] = 8
(6)指向数组的指针(数组指针)
正确的动态二维数组创建方法1:---------很重要 ! ! !
int main()
{
int (*pb[3])[5] = 指向数组的指针数组;/////// int a[3]; pb[1][3]
int (*pc)[5] = malloc(sizeof(pc)*3); //动态二维数组pc[3][5]----sizeof(pc)相当为一个元素的大小; /////// int *p = malloc(sizeof(int)*4);
pb pc
a p
printf("%d\n",sizeof(pc)); //只要是指针变量,那么它用来容纳的就是另一个地址,地址都是4个字节的,所以只要是指针,其大小就为4,而数组名不同,它是一个符号(严格说来并不是指针变量),大小整个数组说明指针变量的大小与指针是什么类型的无关,仅与它是不是指针有关
printf("%p %p\n",pc,pc+1); //说明指针的跨度仅有指针的类型决定
printf("%p%d\n",&pc[1][3],pc[1][3]); /////// pc[1][3]= *(pc+1)[3];
int *p = &pc[0][0];
printf("%p %d\n",&p[8],p[8]); //这样也是可以的,基于内存的访问而不是数组
free(pc);
pc = NULL;
return 0;
}
输出:4
0x87b5008 0x87b501c
0x87b5028 0
0x87b5028 0
正确的动态二维数组创建方法2:---------很重要 ! ! !--------------------这个严格说来并不能算是二维数组
int main()
{
int i , j;
//创建动态二维数组p[3][5] :
int **p = (int **)malloc(sizeof(int *)*3);
for(i= 0;i<3;i++)
{
p[i] = (int *)malloc(sizeof(int)*5);
}
//给动态二维数组赋值:
for(i = 0;i<3;i++)
for(j=0;j<5;j++)
p[i][j] = i+j;
//访问、打印动态二维数组:
for(i = 0;i<3;i++)
{
for(j=0;j<5;j++)
printf("%d\t",p[i][j]);
printf("\n");
}
//释放动态二维数组:
for(i=0;i<3;i++)
{
free(p[i]);
p[i] = NULL;
}
return 0;
}
1 2 3 45
2 3456
则p即为数组指针。*p = int * 类型,即p 相当于int 类型元素的二维数组的数组名。与 int a[10] 、int aa[5][10] 相比,显然p与aa是同一级别的; 即int (*p)[10] 和 int a[10] 比较,p 比a 高一级,是a 的上一级。
所以,p +1 相当于aa+1 ,p[1] 相当 aa[1] ,因为指针的步长等于指针类型的大小,而指向数组的指针的类型为一个数组,所以p 的一个跨度为整个数组(即 sizeof(int)*10的大小);
虽然P的一个跨度为一个数组的大小,但指向数组的指针p仍是一个指针,int (*p)[6] ; sizeof(p)=4, 它的大小仍为4 ,如下所示:-----------很重要!!!
int (*pc)[5];
printf("%d\n",sizeof(pc)); //输出 4 ,说明pc的本质仍是一个指针,
printf("%p %p\n",pc,pc+1);//输出 0xaf4ff4 0 xaf5008 ,刚好相差20个字节,跨度为一个数组的大小
指针的跨度与指针的类型有关,即也是与指针被赋予的地址类型有关,而指针的大小仅与是不是指针相关,与该指针为什么类型无关,所有类型指针的大小都为4
int aa[5][10];
int a[10];
int (*p)[10] = aa ;
或者可以int (*p)[10] = &a ; // a ----- 元素地址,一个元素的大小 ; &a ---- 数组地址,一整个数组的大小
而 p+1 与 p[1] 与 aa+1 与aa[1] 是等价的;
则:*(*p+3) 与*(p[0]+3) 与 *(aa[0]+3) 与 *(*aa+3) 与 *(a+3) 与a[3] 是等价的。
如以下程序:
//int (*p)[10] 与二维指针aa:
int aa[5][10] = {{1,2,3,4,5,6,7,8,9,0},
{10,20,30,40,50,60,70,80,90,100},
{11,12,13,14,15,16,17,18,19,20}};
int (*p)[10] = aa; // p 与aa一个级别,aa的值为一维数组的地址,而p也用于存储一维数组的地址
printf("aa:%p p:%p\n",aa,p);
printf("aa+1:%p ; p+1:%p\n",aa+1,p+1); //总共偏移40 个字节
printf("aa[1]:%p ; p[1]:%p\n",aa[1],p[1]); //总共偏移40 个字节
printf("*(aa[1]+3) = %d ; *(*(p+1)+3) = %d\n\n", *(aa[1]+3),*(*(p+1)+3));
输出结果:
aa:0xbfb18b08 p:0xbfb18b08
aa+1:0xbfb18b30 p+1:0xbfb18b30
aa[1]:0xbfb18b30 p[1]:0xbfb18b30
*(aa[1]+3) = 40 *(*(p+1)+3) = 40
// int (*p)[10] 与一维指针a:
int b[10] = {11,22,33,44,55,66,77,88,99,110}; // b为一级元素int的地址,&b为一维数组的地址
int (*pb)[10] = &b; //虽然&b 和 b 输出的地址是相同的,但&b 和 b 的意义是完全不同的 ,就如同 aa 和 &aa[1] 的关系,一个是int (*p)[ ],一个是int *
//虽然b 是一个数组名也是一个指针,但&b并不是返回这个指针的地址,而是返回b的值,&b与b的值是相等的,这就是数组名与指针的差别!
printf("&b:%p b:%p\n",&b,b);
printf("*(*pb+3) = %db[3]) = %d\n",*(*pb+3),b[3]);
printf("*(pb[0]+3) = %db[3]) = %d\n\n",*(pb[0]+3),b[3]);
输出结果为:
&b:0xbfb18bd0b:0xbfb18bd0
*(*pb+3) = 44 b[3]) = 44 // *(*pb+3) =*(pb[0]+3)
*(pb[0]+3) = 44b[3]) = 44
(7)malloc 返回的指针
void * malloc( int size);
malloc开辟的是堆区(专门用于malloc)的地址空间 ,需要用户指定开辟空间的大小,同时在开辟后会立即返回此开辟空间的首地址,返回类型为void * ,这样开辟的地址就可以用于各种类型数据的存放,那么对应的返回的void * 指针也要转变成各种指针类型。因为数据在内存中的存放实际上是没有类型之说的,有类型的只是我们的操作,只是我们在读取内存或存放数据至内存时,是根据变量或指针的类型宽度来指定读取内存的大小和分配存放数据的空间大小的,我们依照我们定义的变量或指针类型来访问空间,而空间本身是没有类型的,只是我们在访问时指定了访问的类型。所以,对于malloc返回的指针void * , 我们直接使用的话是没有意义的,因为它对应的是空类型void ,没有指定类型宽度,所以我们访问起来也是没办法指定对内存操作的宽度的,所以我们需要对malloc返回的指针类型进行类型转换。
所谓的类型转换有以下两种方式:
如: int * p = malloc(sizeof(int)*10); 由赋值操作符自动进行类型转换;
int * p = (int *)malloc(sizeof(int)*10); 强制类型转换;
一般我们使用后者——强制类型转换 !这样比较保险。
因为指针的步长等于指针类型的大小(即sizeof( type)), 所以我们可以通过移动指针p+1或p+i 来达到存取相应类型数据的目的。同样的,这个也适用与由malloc返回的经过强制类型转换的指针。这样的话,也就可以使用malloc构建一个数组,而返回的指针p相当于数组名使用;也可以用来开辟一个字符串空间,用于存储字符串,这样p就相当于一个普通的指向字符串的指针char *。不过,更经常的是用malloc 构建一个结构体。
因为malloc返回的地址空间上的内容的初始值是不确定的,所以为确保用的时候不会出问题,最好在每个malloc后面都要 memset(p,0,size);一下。
还有就是最终都别忘了free 和 赋为NULL。
以下是这三种情况下的通常用法:
a、用于动态数组:----- 用malloc创建的数组称为动态数组,大小在运行时才知道,在运行时分配空间;
用a[ ]下标创建的数组称为静态数组,大小在编译时就知道了,在编译时分配空间;
全局变量数组、局部静态变量数组、局部动态变量数组都——称作静态数组,前两个在静态区,最后一个在栈区。
int * p = (int *)malloc( sizeof(int) * 10 );
memset(p ,0,sizeof(int)*10 );
for( int i = 0;i<10;i++)
p[i] = i; // ( 或 *(p+i) = i )
int * dexp = p;
printf("%d%d\n", p[3] ,*(dexp+3)); // 输出 33 ;
free(p);
p = NULL;
——常与指向数组的指针如int (*p)[ ]连用——用于创建动态二维数组:
动态二维数组与指针:
//错误的动态二维数组创建方法:
//int *b = (int *)malloc(sizeof(int)*5*7); //模拟b[7][5]
//int **pb = &b;//本身是没错,但是要作为模拟二维数组的话就是错的,因为二维数组名根本就不是int **类型!!!
//int (*pbb)[5] = &b;//错误!!! &b是二级指针,而pbb 是二维数组名类别的,这就是对一个指向第一元素的指针取地址和对一个数组名取地址的区别——地址名并不是一个指针而只是一个符号,虽然关联的都是第一元素的地址,但很多数组名的性质就是不能用于一般指针!!!
//printf("%p%p%p\n",&b[10],&pbb[1][3],&pb[1][3]);
1. 正确的动态二维数组创建方法1:---------很重要 ! ! !
int main()
{
int (*pc)[5] = malloc(sizeof(pc)*3); //动态二维数组pc[3][5]
printf("%d\n",sizeof(pc)); //说明指针的大小与指针是什么类型的无关,仅与它是不是指针有关
printf("%p%p\n",pc,pc+1); //说明指针的跨度仅有指针的类型决定
printf("%p %d\n",&pc[1][3],pc[1][3]);
int *p = &pc[0][0];
printf("%p%d\n",&p[8],p[8]); //这样也是可以的,基于内存的访问而不是数组
free(pc);
pc = NULL;
return 0;
}
输出:4
0x93e7098 0x93e70ac
0x93e70b8 0
0x93e70b8 0
2. 正确的动态二维数组创建方法2:---------很重要 ! ! !
int main()
{
int i , j;
//创建动态二维数组p[3][5] :
int **p = (int **)malloc(sizeof(int *)*3);
for(i= 0;i<3;i++)
{
p[i] = (int *)malloc(sizeof(int)*5);
}
//给动态二维数组赋值:
for(i = 0;i<3;i++)
for(j=0;j<5;j++)
p[i][j] = i+j;
//访问、打印动态二维数组:
for(i = 0;i<3;i++)
{
for(j=0;j<5;j++)
printf("%d\t",p[i][j]);
printf("\n");
}
//释放动态二维数组:
for(i=0;i<3;i++)
{
free(p[i]);
p[i] = NULL;
}
return 0;
}
输出: 01234
12345
23456
b、用于字符串:
char * s = (char *)malloc(100);
memset(s ,0,100 );
strcpy(s,"abcdefg");
printf("%s\n",s);
s[3] = '4'; // 要注意确保 s[3]中的3要在s的字符串长度(strlen(s)) 以内 ,否则无效( 3 位置在‘\0’的后面)或异常(3位置正好处于'\0'的位置,把‘\0’给覆盖掉了)
printf("s[3] = %c\n",s[3] );
free(p);
p = NULL;
注意:使用strcpy、memset 、memcpy 等涉及到修改空间内容的函数或操作(如s[i] = ‘m’)时,要确保指针指向的空间是合法而且有效(不能是野指针)的,即已 经由系统分配了(变量或数组的地址)或是手动开辟(malloc得到的空间地址)过了,否则会出现段错误!
如:char *s ;
strcpy(s,"abcd"); // 错误!!--- 会出现不可预知的错误,因为这时的s是野指针!---- 编译会通过,因为编译器无法察觉野指针,但执行时会段错误!
或:char *s = "1234";
strcpy(s,"abcd");//也是错误的,因为s指向的空间是字符串常量的空间,
注意:若是用使用strcpy、memset 、memcpy 等涉及到修改空间内容的函数或操作(如s[i] = ‘m’)时,要看看原有字符串的‘\0’ 有没有被覆盖掉,或者是cpy时没有将原字符串的尾部‘\0’赋值过来,如果这样的话,要手工给新字符串的末尾添一个‘\0’ ! ! ! !
如:char s1[ 6 ] = "abcd";(全局区)
s1[4] = '5';
printf("%s\n",s1);//正确,因为s1在初始化时除了前面4个,后面的都被初始化为了0 即 '\0' ,所以第5个位置被覆盖为‘5’,也还有第6个为'\0'
s1[5] = '6';//最后一个预留给'\0' 的位置被覆盖了!
printf("%s\n",s1);//最后一个预留给'\0' 的都被覆盖了,怎么还输出正常? ? ?------因为也许恰巧s1[8]是‘\0’
s1[7] = '7'; //越界了,系统崩溃 !
printf("%c\n",s1[7]);
c、用于结构体:
typedef struct stu1{ typedef struct stu2{
char *name; char name[20];
int id; int id;
int number[12]; int number[12];
}Stu1; }Stu2;
要注意:stu1 和 stu2 并不一样,不一样在于name的构建不同,前者只是构建一个指针,而后者构建了一个数组,对字符串的构建方式不同,所以这两种结构体类型大小也不同,之后初始化的方式也不同,这个要注意一下:
大小: sizeof(Stu1) = 56 ; sizeof(Stu2) = 72;
因为Stu1中的name只是一个指针,指针只占4个字节 ; 而Stu2中的name为一个数组,编译时编译器已经为其开辟了空间,所以为大小20个字节
字节对齐:结构体类型的大小不仅与成员有关,还与成员的顺序有关,涉及到字节对齐问题,
可参考:http://blog.csdn.net/21aspnet/article/details/6729724(原版)
http://blog.csdn.net/u011170660/article/details/38016119(转载过来的)
初始化:结构体变量的初始化只能在定义结构体变量的时候同时进行,因为不允许对结构体的单个成员进行初始化。
结构体初始化的两种方式如下所示:
在定义结构体类型的同时定义结构体变量时:
struct Stu1{
…… ;
}stu1 = {"Elain",11,{12345678901}}; // 注意:用typedef 的时候是不允许在定义结构体类型的同时定义结构体变量的 !
在函数内单独定义结构体变量时:
struct Stu1 stu1 = {s,11,{12345678901}};这里初始化name时即可以用另一字符串指针也可用字符串常量,因为在函数体内定义以及可以获 得字符串指针了,而上面的情况因为是在体外初始化无法获得指针变量,所以只能用”Elain“了 !
struct Stu2 stu2 = {”Soha“,11,{12345678901}}; 这里只能用字符串"Soha",因为stu2->name是数组名而不是指针 !
而又因为结构体变量和其他类型变量一样,可以在定义的同时完成系统默认的初始化(实质是成员各自按照全局时的默认初始化),所以我们也可以不在定义的时候马上去初始化它,让它自动初始化,然后在之后再一一对每个成员进行赋值就可以了,这样也完成了类似的初始化功能啦!
所以以下使用的是对成员一一赋值的方法:
Stu1 *stu1;// name自动初始化为NULL ! , id 默认初始化为0 ,number 默认初始化为{0};
Stu2 *stu2;// name自动初始化为{0} , id 默认初始化为0 ,number 默认初始化为{0};
stu1 = (Stu1 *)malloc(sizeof(Stu1 ));
stu2 = (Stu2 *)malloc(sizeof(Stu2 ));
stu1->name = (char *)malloc(20);
stu1->name = "Elaine"; //只有在对字符数组的初始化时可以这么用,其它时候不可以! ! !
stu2->name = "Soha“;//只有在对字符数组的初始化时可以这么用,其它时候不可以! ! !
strcpy(stu1->name,"Elaine");// 字符串的赋值只能用strcpy来完成 !
strcpy(stu2->name,"Soha"); // 字符串的赋值只能用strcpy来完成 !
free(stu1); stu1 = NULL;
free(stu2); stu2 = NULL;
因为stu1中的name 仅仅只是一个指针,还没有开辟存储字符串的空间,所以在对stu1->name进行赋值前要先对name进行malloc;
而stu2中的name 因为是一个数组,所以空间已经是开辟好了的,所以直接使用即可。
(8)函数指针
初始化:
------ 用相同类型(返回值和形参列表相同)函数的函数名或函数指针
printf("%p\n", test); //函数名或函数指针其实就是一函数的入口地址,也就是一指针,叫为函数指针
printf("%p\n",&test); //对函数指针或函数名进行求地址或解引用,也能得到其函数入口值
printf("%p\n", *test);
函数名,与数组名类似(注意:只是类似),是一个符号用来标识一个函数的入口地址
用 typedef 定义函数类型:
定义:typedef int (*ptf) (double*, char);// ------把 ptf 定义为一种函数指针类型的别名,它和函数指针类型int (*) (double*, char);等价,也就是说ptf现在也是一种类型。
使用:ptf fun1(int );// ------这样就变成了定义一个返回值为函数指针的函数,函名为function,参数为int
函数指针的复杂用法:
作为返回值:定义:typedef int (*ptf) (double*, char);
ptf function(int );//这样就变成了定义一个返回值为函数指针的函数,函名为function,参数为int
使用: int ( * function(int)) (double*,char);
作为函数参数:定义:typedef void (*p_sig) (int);
使用:void signal( int i, p_sig func);
即作为返回值又作为函数参数:
如:void (*signal (int sig, void (*func) (intsiga))) ( int siga );
如:typedef void (*p_sig) (int);
p_sig signal(int sig, p_sig func);
函数指针数组:
------ 通过对指针action进行下标操作可以调用数组中的任一操作,如:action[2]( )会调用write操作。
PF file_options[ ] = { //file_options 可以看成是函数指针的指针,数组元素为函数指针
&open,
&read,
&write,
&close
};// 通过下标来选择要调用的函数,可把一类操作放在同一个函数指针数组里面,或是将对同一个对象的操作放在同一个函数数组里面:
如:void do_file( const char * filename , char *buf ,void (*f)(const char *filename,char * buf));
使用: do_file("text",NULL,file_options[0 ]);
do_file("text",get_buf,file_options[1 ]);
do_file("text",write_buf,file_options[2 ]);
do_file("text",NULL,file_options[3 ]);
指针的复杂混合:
可以用从外剥离法找到最里面,用右左法则扩展到最外面的方法:
1. int *( *( *a[5]) ( ) ) ( );
2. void * (*b) ( char, int (*) ( ) );
3. float ( *(*c[10]) (int*) ) [5];
4. int ( *(*d)[2][3] ) [4][5];
5. int (*(*(*e) ( int* ))[15]) (int*);
6. int ( *(*f[4][5][6]) (int*) ) [10];
7. int *(*(*(*g)( ))[10]) ( );
参考答案查看:点击
一、常量指针与一般指针与const 对象的地址与指针常量 间的赋值关系:
---------------只要赋值操作符两边的指针所指向的空间的类型相包含(左包含右) 的原则即可 :
const int bb = 4;
int *pi = &aa;
int *pc = &bb;// 错!!不能将const 对象的地址赋给一般指针
const int *cpi,*cpc;
cpi= &aa;//常量指针既可以指向一般变量,也可以指向const 变量
cpc = &bb;//常量指针既可以指向一般变量,也可以指向const 变量
cpi =pi;//而实际上任何指针(一般指针、常量指针、指针常量)都可以赋给常量指针
int *p1,*p2;
p1 = cpi;//不能将常量指针(不管指向的是const对象还是非const对象)赋给一般指针,除非强制转换为一般指针
p2 = cpc;//不能将常量指针(不管指向的是const对象还是非const对象)赋给一般指针,除非强制转换为一般指针
p1 = &bb;//不能将const 对象的地址赋给一般指针,除非强制转换为一般指针----即切记一般指针不能指向const 变量
p2 = &bb;//不能将const 对象的地址赋给一般指针,除非强制转换为一般指针
int i = 0; //定义一个整型变量并初始化为0
const int ci = 0; //定义一个只读的整型变量并初始化,程序中不能再对它赋值
int *p = NULL; //定义一个整型指针并初始化为NULL
const int *p_to_const = NULL;//定义一个常量指针,初始化为NULL
p_to_const = &ci; //ok,p_to_const 也可以指向const变量,也可以指向非const变量的 、//ok,让p_to_const指向ci
p_to_const = &i; //ok,p_to_const 也可以指向const变量,也可以指向非const变量的 /ok,让指向const对象的指针指向 普通对象
p_to_const = p; //ok,将指向普通对象的指针赋给常量指针
p = &ci; //error,不能将const变量的地址赋给一般指针
p = p_to_const; //error,不能将常量指针赋给一般指针
p = (int *) &ci; //ok,强制转化为(int *)型,赋值操作符两侧操作数类型相同
p = (int *) p_to_const; //ok,同上
C语言中对于指针的赋值操作(而不是对象的复制操作)(包括实参与形参之间的传递)应该满足:
应该满足:两个操作数都是指向有限定符 或 指向无限定符的类型相兼容 或 左边指针所指向的类型具有右边指针所指向的类型的全部限定符。
即:只要满足左边指针指向的空间类型(包含)右边指针所指向的空间类型即可!!!
如int *const p1 = const int *p2 、int *p3 = p2 就不可以! , 而int *p3 = int *const p1就可以!
比较的应该是 : 赋值操作符两边的指针所指向的空间类型,与指针本身是否是常量无关,所以int * const p1 看的只是int * ,而与const没有关系, 因为const修饰的是指针而不是指针所指向的空间。
例如 : const int *表示“指向一个具有const限定符的int类型的指针”,即const所修饰的是指针所指向的类型,而非指针。因此,p = ⁣ 中的&ic得到的是一个指向const int型变量的指针,类型和p_to_const一样。p_to_const所指向的类型为const int,而p所指向的类型为int,p在赋值操作符左边,p_to_const在赋值操作符右边,左边指针所指向的类型并不具有右边指针所指向类型的全部限定符,所以会出错。
所以,按照赋值操作符两边的指针所指向的空间的类型相包含(左包含右)的原则 :
//指针常量
/*
指针常量只允许初始化,不允许被赋值。
绝不存在指向const 变量的一般指针,因为不能将const 变量的地址赋给一般指针!!!!!
==按照赋值操作符两边的指针所指向的空间的类型相包含(左包含右) 的原则:
*/
//初始化:
int aa = 3;
const int bb = 6;
int *const p1 = &aa;//允许,因为p1 所指向的地址空间类型为int ,和&aa 地址的空间类型int 是一样的,而int *const p1 的const 只是修饰指针,与地址空间的性质无关
// int *const p2 = &bb;//不允许,因为&bb 是const int 类型的指针,而p2 指向的是一般类型int 的变量,赋值等号两边包含关系不满足
printf("%d\n",*p1);
int *pp = &aa;
const int *pp1 = &bb;
int * const p3 = pp;
// int * const p4 = pp1;//不允许,因为p4 所指向的空间类型为int,而pp1 是常量指针,指针所指向的空间类型为const int ,p4 指向的空间类型不能保含pp1指向的空间类型
int * const p5 = p1;
//将指针常量赋值给其它指针:
pp = p1;//允许,因为pp指向的空间类型为int , 而p1 指向的空间类型也为int ,两者一样。
pp1 = p1;//允许,因为pp1 指向的空间为const int ,而p1 指向的空间类型为int ,const int包含int,所以可行。
------------------------------------------------------------------------------------------------------------------------
#define (void *)0 NULL
int *p = 0; 正确
int *p = 2-2; 正确
int *p = 0x0000; 正确 int *p = (int *)0x0000; 正确
int *p = 258; 错误 int *p = (int *)258; 正确
int *p = 0x11010110; 错误int *p = (int *)0x11010110; 正确
int *p = NULL; 正确
char *cp = “abcdefg”;正确
-------------------------------------------------------------------------------------------------------------------------------------------------------------------C语言中,指针是最难理解的一部分,尤其是Const指针变量。
Const指针变量只保护其Value值不被改变,并不能保护指针所指向的地址中的值不被改变,下面是针对Const指针的一个小练习:
//定义基本类型的const变量,const 位置在哪儿都可以
const int x = 2,y = 3; //两个常量
//定义一个非const变量
int z = 3; //一个普通变量
//定义一个指向常的指针
const int* pc = &x; //指向常量的常量指针
//定义一个常指针
int* const cp = &z; //常指针
//定义一个非const指针
int* pp = &z; //int 型指针
// x = y; //x 为const变量,所以x的值不可以被更改 只能做右值
pc = &z; //可以,pc是一个指向常量的指针,不能通过该指针去修改指针所指向的内存空间的值,但是,该指针可以指向别的变量
// *pc = 10; //不可以,*pc所指向的地址为const变量,其值不可更改 pc是一个指向常量的指针,不能通过该指针去修改指针所指向的内存空间的值
// *pc = y; //不可以,同上
// *pc = 5; //同上
pc = pp; //可以,pc为指针,其值可被改变 pc是一个指向常量的指针,pp是一个普通指针,用一个普通指针给一个指向常量的指针赋值是可以的
// pp = pc; //用一个指向常量的指针,赋值给一个普通指针,不可以。如果可以,那么就可以通过普通的指针去修改内存的值
z = 5; //可以,给一个非const变量赋值是可以的
z = x; //可以,用一个const的变量给一个非const变量赋值是可以的
*cp = x; //可以,通过常指针去修改指针所指向变量的值,原则上来讲是可以的,如果指向的变量是const的,那么不同编译器会有不同的结果
// cp = &x; //不可以,cp为常指针,指针的值不能被修改,给常指针赋值是错误的
// pp = &x; //不可以,pp是非const指针,原则上来讲给它赋值是可以的,在不同的编译器下有不同的结果
// pp = pc; //不可以,指向常量的指针不能赋值给一个普通的指针
pp = cp; //可以,常指针可以赋值给一个普通指针
const int * const pp = &a; //双const 既保护指针又保护内容
=============================================================================================
#define (void *)0 NULL
int *p = 0; 正确
int *p = 2-2; 正确
int *p = 0x0000; 正确
int *p = (int *)0x0000; 正确
int *p = 258; 错误 int *p = (int *)258; 正确
int *p = 0x11010110; 错误int *p = (int *)0x11010110; 正确
int *p = NULL; 正确
char *cp = “abcdefg”;正确
#define NULL 0;所以不能对NULL指针(零地址)进行解引用(*p)(访问)——因为对零地址进行访问会报错!。不过char *p = NULL;则if(p)、if(!p)、while(p)、while(!p) 是可以的。
void *型指针作为一种通用的指针,可以和其它任何类型的指针(函数指针除外)相互转化而不需要类型强制转换, 但不能对它进行解引用及下标操作(因为void *无类型大小,无法访问!);
指向const对象的一般指针---是不允许的, 只允许存在指向const对象的常量指针。
虽然指向const对象的一般指针时不允许的,但指向常量字符串的一般指针时允许的,虽然都是常量,但一个是const修饰的常量,一个是静态存储区(txt)常量,虽然性质都是不允许修改的常量性质,但从其存储上看,其本质还是不同的。
- 各种指针分类总结 + 指针间的赋值关系
- C-指针赋值总结
- C++ 指针 new delete 赋值各种情况总结
- 指针间的关系
- 小心指针间的赋值
- 各种指针类型总结
- 指针以及指针的分类
- 指针的赋值问题
- 指针的赋值
- 指针赋值的问题
- 指针赋值的问题
- 指针的错误赋值
- 3. 指针的赋值
- 指针赋值的问题
- 指针的赋值运算
- 指针变量的赋值
- C中各种指针的总结
- 指针的分类
- TINY4412 驱动开发学习笔记整理---内存管理篇
- VB.NET版机房收费系统---外观层如何写
- CentOS下源码安装PostgreSQL
- tomcat 7 源码分析
- jQuery Fancybox插件使用参数详解
- 各种指针分类总结 + 指针间的赋值关系
- windows内核模式开发常用知识
- 统一D3D与OpenGL坐标系统
- Android的多媒体数据库
- 开源夏令营Memcached哈希性能优化(二)
- Hibernate的检索方式
- Ubuntu下VMware无法保存配置文件问题
- unity 我的第一个项目总结(进行中)
- Linux原子操作 (Linux atomic operations)