赞
踩
1、指针变量和普通变量的区别
(1)首先必须非常明确:指针的实质就是个变量,它跟普通变量没有任何本质区别。指针完整的名字应该叫指针变量,简称为指针。
代码:
// a的实质其实就是一个编译器中的符号,在编译器中a和一个内存空间联系起来
// 这个内存空间就是a所代表的那个变量。
int a; // 定义了int型变量,名字叫a
int *p; // 定义了一个指针变量,名字叫p,p指向一个int型变量
a = 4; // 可以操作
p = 4; // 编译器不允许,因为指针变量虽然实质上也是普通变量,但是它的
// 用途和普通变量不同。指针变量存储的应该是另外一个变量的地址
// 而不是用来随意存一些int类型的数。
p = (int *)4; // 我们明知道其实就是数字4,但是我强制类型转换成int *类型的4
// 相当于我告诉编译器,这个4其实是个地址(而且是个int类型变量
// 的地址),那么(int *)4就和p类型相匹配了,编译器就过了。
2、为什么需要指针?
3、指针使用三部曲:定义指针变量、关联指针变量、解引用
代码:
// 演示指针的标准使用方式 // 指针使用分3步:定义指针变量、给指针变量赋值(绑定指针)、解引用 int a = 23; // 第一步,定义指针变量 int *p; printf("p = %p.\n", p); // %p打印指针和%x打印指针,打印出的值是一样的 printf("p = 0x%x.\n", p); // 第二步,绑定指针,其实就是给指针变量赋值,也就是让这个指针指向另外一个变量 // 当我们没有绑定指针变量之前,这个指针不能被解引用。 p = &a; // 实现指针绑定,让p指向变量a p = (int *)4; // 实现指针绑定,让p指向内存地址为4的那个变量 // 第三步,解引用。 // 如果没有绑定指针到某个变量就去解引用,几乎一定会出错。 *p = 555; // 把555放入p指向的变量中
1、星号*
2、取地址符&
int a;
int *p;
p = &a;
3、指针定义并初始化、与指针定义然后赋值的区别
int a = 32; int *p = &a;
int a = 32; int *p; p = &a;
正确的 ,*p = &a;
错误的4、左值与右值
1、神马是野指针?
(1)野指针,就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
(2)野指针很可能触发运行时段错误(Sgmentation fault)
(3)因为指针变量在定义时如果未初始化,值也是随机的。指针变量的值其实就是别的变量(指针所指向的那个变量)的地址,所以意味着这个指针指向了一个地址是不确定的变量,这时候去解引用就是去访问这个地址不确定的变量,所以结果是不可知的。
(4)野指针因为指向地址是不可预知的,所以有3种情况:第一种是指向不可访问(操作系统不允许访问的敏感地址,譬如内核空间)的地址,结果是触发段错误,这种算是最好的情况了;第二种是指向一个可用的、而且没什么特别意义的空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间),这时候程序运行不会出错,也不会对当前程序造成损害,这种情况下会掩盖你的程序错误,让你以为程序没问题,其实是有问题的;第三种情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用(譬如说是程序的一个变量x),那么野指针的解引用就会刚好修改这个变量x的值,导致这个变量莫名其妙的被改变,程序出现离奇的错误。一般最终都会导致程序崩溃,或者数据被损害。这种危害是最大的。
(5)指针变量如果是局部变量,则分配在栈上,本身遵从栈的规律(反复使用,使用完不擦除,所以是脏的,本次在栈上分配到的变量的默认值是上次这个栈空间被使用时余留下来的值),就决定了栈的使用多少会影响这个默认值。
2、怎么避免野指针?
(1)野指针的错误来源就是指针定义了以后没有初始化,也没有赋值,然后去解引用。
(2)知道了野指针产生的原因,避免方法就出来了:在指针的解引用之前,一定确保指针指向一个绝对可用的空间。
(3)常规的做法是:
第一点:定义指针时,同时初始化为NULL
第二点:在指针解引用之前,先去判断这个指针是不是NULL
第三点:指针使用完之后,将其赋值为NULL
第四点:在指针使用之前,将其赋值绑定给一个可用地址空间
int b,a=5;
int *p = NULL;
p = &a; // 正确的使用指针的方式,是解引用指针前跟一个绝对可用的地址绑定
if (NULL != p)
{
b=*p ;
}
p = NULL; // 使用完指针变量后,记得将其重新赋值为NULL
3、NULL到底是什么?
#ifdef _cplusplus // 定义这个符号就表示当前是C++环境
#define NULL 0 // 在C++中NULL就是0
#else
#define NULL (void *)0 // 在C中NULL是强制类型转换为void *的0
#endif
(2)在C语言中,int *p;
你可以p = (int *)0;
但是不可以p = 0;
因为类型不相同。
(3)所以NULL的实质其实就是0,然后我们给指针赋初值为NULL,其实就是让指针指向0地址处。为什么指向0地址处?原因是0地址处作为一个特殊地址,这个0地址在一般的操作系统中都是不可被访问的,如果C语言程序员不按规矩(不检查是否等于NULL就去解引用)写代码直接去解引用就会触发段错误,提醒程序员。
(4)一般在判断指针是否野指针时,都写成if (NULL != p)
而不是写成if (p != NULL)
原因是:如果NULL写在后面,当中间是双=号的时候,有时候容易忘记写成了=,这时候其实程序已经错误,但是编译器不会报错。这个错误(对新手)很难检查出来;如果习惯了把NULL写在前面,当错误的把双=写成了=时,编译器会报错,程序员会发现这个错误。
1、从内存角度来理解数组
2、从编译器角度来理解数组
3、数组中几个关键符号(a ,a[0] ,&a ,&a[0])的理解(前提是定义了一个数组int a[10])
(1)这4个符号搞清楚了,数组相关的很多问题都有答案了。理解这些符号的时候要和左值右值结合起来,也就是搞清楚每个符号分别做左值和右值时的不同含义。
(2)a就是数组名。a做左值时表示整个数组的所有空间,又因为C语言规定数组操作时要独立单个操作,不能整体操作数组,所以a不能做左值;a做右值表示数组首元素的首地址(首地址就是起始地址,int数组中就是4个字节中最开始第一个字节的地址)。a做右值等同于&a[0];
(3)a[0]表示数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间(连续4字节);做右值时表示数组第0个元素的值.
(4)&a就是数组名a取地址,字面意思来看就应该是数组的地址。&a不能做左值(&a实质是一个常量,不是变量因此不能赋值,所以自然不能做左值。);&a做右值时表示整个数组的首地址。
(5)&a[0]字面意思就是数组第0个元素的首地址(搞清楚[]和&的优先级,[]的优先级要高于&,所以a先和[]结合再取地址)。做左值时表示数组首元素对应的内存空间,做右值时表示数组首元素的值。做右值时&a[0]等同于a。
解释:为什么数组的地址是常量?因为数组是编译器在内存中自动分配的。当我们每次执行程序时,运行时都会帮我们分配一块内存给这个数组,只要完成了分配,这个数组的地址就定好了,本次程序运行直到终止都无法再改了。那么我们在程序中只能通过&a来获取这个分配的地址,却不能去用赋值运算符修改它。
总结:
4、以指针方式来访问数组元素
5、从内存角度理解指针访问数组的实质
6、指针和数组类型的匹配问题
(1)int *p; int a[5]; p = a; // 类型匹配
(1)int *p; int a[5]; p = &a; // 类型不匹配。p是int *,&a是整个数组的指针,也就是一个数组指针类型,不是int指针类型,所以不匹配
7、指针类型决定了指针如何参与运算
1、变量的数据类型的含义
所有的类型的数据存储在内存中,都是按照二进制格式存储的,所以内存中只知道有0和1。
int、char、short等属于整形,他们的存储方式(数转换成二进制往内存中放的方式)是相同的,只是内存格子大小不同(所以这几种整形就彼此叫二进制兼容格式);而float和double的存储方式彼此不同,和整形更不同。
例如int a = 5;
时,编译器给a分配4字节空间,并且将5按照int类型的存储方式转成二进制存到a所对应的内存空间中去(a做左值的);我们printf去打印a的时候(a此时做右值),printf内部的vsprintf函数会按照格式化字符串所代表的类型去解析a所对应的内存空间,解析出的值用来输出。
也就是说,存进去时是按照这个变量本身的数据类型来存储的(譬如本例中a为int所以按照int格式来存储);但是取出来时是按照printf中%d之类的格式化字符串的格式来提取的。此时虽然a所代表的内存空间中的10101序列并没有变(内存是没被修改的)但是怎么理解(怎么把这些1010转成数字)就不一定了。
譬如我们用%d来解析,那么还是按照int格式解析则值自然还是5;但是如果用%f来解析,则printf就以为a对应的内存空间中存储的是一个float类型的数,会按照float类型来解析,值自然是很奇怪的一个数字了。
总结:C语言中的数据类型的本质,就是决定了这个数在内存中怎么存储的问题,也就是决定了这个数如何转成二进制的问题。一定要记住的一点是内存只是存储1010的序列,而不管这些1010怎么解析。所以要求我们平时数据类型不能瞎胡乱搞。
分析几个题目:
1. 按照int类型存却按照float类型取 一定会出错
2. 按照int类型存却按照char类型取 有可能出错也有可能不出错
3. 按照short类型存却按照int类型取 有可能出错也有可能不出错
4. 按照float类型存却按照double取 一定会出错
2、指针的数据类型的含义
3、指针数据类型转换实例分析1
int * -> char *
4、指针数据类型转换实例分析2
(int * -> float *)
1、char str[] = ”hello”; sizeof(str) ,sizeof(str[0]) ,strlen(str)
char str[] = "hello";
printf("sizeof(str) = %d.\n", sizeof(str)); // 6
printf("sizeof(str[0]) = %d.\n", sizeof(str[0])); // 1
printf("strlen(str) = %d.\n", strlen(str)); // 5
char str[] = "hello";
char *p = str;
printf("sizeof(p) = %d.\n", sizeof(p)); // 4 相当于sizeof(char *)
printf("sizeof(*p) = %d.\n", sizeof(*p)); // 1 相当于sizeof(char)
printf("strlen(p) = %d.\n", strlen(p)); // 5 相当于strlen(str)
2、int n=10; sizeof(n)
int n = 10;
printf("sizeof(n) = %d.\n", sizeof(n)); // 4
printf("sizeof(int) = %d.\n", sizeof(int)); // 4
3、int b[100]; sizeof(b)
int b1[100] = {0};
printf("sizeof(b1) = %d.\n", sizeof(b1)); // 400 100×sizeof(int)
short b2[100] = {};
printf("sizeof(b2) = %d.\n", sizeof(b2)); // 200 100×sizeof(short)
double b3[100];
printf("sizeof(b3) = %d.\n", sizeof(b3)); // 800 100×sizeof(double)
4、数组传参
void fun(int b[100])
{
sizeof(b);
}
5、define 和 typedef的一些细节
#define dpChar char *
typedef char * tpChar;
dpChar p1, p2; // 展开:char *p1, p2; 相当于char *p1, char p2;
tpChar p3, p4; // 等价于:char *p3, char *p4;
printf("sizeof(p1) = %d.\n", sizeof(p1)); // 4
printf("sizeof(p2) = %d.\n", sizeof(p2)); // 1
printf("sizeof(p3) = %d.\n", sizeof(p3)); // 4
printf("sizeof(p4) = %d.\n", sizeof(p4)); // 4
1.const关键字
2.const关键字应用
3、const修饰指针的4种形式
int a = 5; // 第一种 const int *p1; // p本身不是cosnt的,而p指向的变量是const的 // 第二种 int const *p2; // p本身不是cosnt的,而p指向的变量是const的 // 第三种 int * const p3; // p本身是cosnt的,p指向的变量不是const的 // 第四种 const int * const p4;// p本身是cosnt的,p指向的变量也是const的 *p1 = 3; // error: assignment of read-only location ‘*p1’ p1 = &a; // 编译无错误无警告 *p2 = 5; // error: assignment of read-only location ‘*p2’ p2 = &a; // 编译无错误无警告 *p3 = 5; // 编译无错误无警告 p3 = &a; // error: assignment of read-only variable ‘p3’ p4 = &a; // error: assignment of read-only variable ‘p4’ *p4 = 5; // error: assignment of read-only location ‘*p4’
4、const修饰的变量真的不能改吗?
const int a = 5;
//a = 6; // error: assignment of read-only variable ‘a’
int *p;
p = (int *)&a; // 这里报警高可以通过强制类型转换来消除
*p = 6;
printf("a = %d.\n", a); // a = 6,结果证明const类型的变量被改了
5、const究竟应该怎么用
1、普通变量作为函数形参
void func1(int b)
{
// 在函数内部,形参b的值等于实参a
printf("b = %d.\n", b);
printf("in func1, &b = %p.\n", &b);
}
int main(void)
{
int a = 4;
printf("&a = %p.\n", &a); // &a = 0x7ffc3826c2f4.
func1(a); //b = 4, in func1, &b = 0x7ffc3826c2dc.
return 0;
}
结论: &a和&b不同,说明a和b不是同一个变量(在内存中a和b是独立的2个内存空间)但是a和b是有关联的,实际上b是a赋值得到的。
2、数组作为函数形参
void func2(int a[])
{
printf("sizeof(a) = %d.\n", sizeof(a));
printf("in func2, a = %p.\n", a);
}
int main(void)
{
int a[5];
printf("a = %p.\n", a); //a = 0x7ffc5e60ef00.
func2(a); //sizeof(a) = 8. in func2, a = 0x7ffc5e60ef00.
//用到的是64位操作系统,所以sizeof(a)是8
return 0;
}
3、指针作为函数形参
void func3(int *a)
{
printf("sizeof(a) = %d.\n", sizeof(a));
printf("in func2, a = %p.\n", a);
}
int main(void)
{
int a[5];
printf("a = %p.\n", a);
func3(a);
return 0;
}
4、结构体变量作为函数形参
结构体变量传参:
struct A { char a; // 结构体变量对齐问题 int b; // 因为要对齐存放,所以大小是8 }; void func4(struct A a1) { printf("sizeof(a1) = %d.\n", sizeof(a1)); printf("&a1 = %p.\n", &a1); printf("a1.b = %d.\n", a1.b); } int main(void) { struct A a = { .a = 4, .b = 5555, }; printf("sizeof(a) = %d.\n", sizeof(a)); //sizeof(a) = 8. printf("&a = %p.\n", &a); //&a = 0x7ffc93bee420. printf("a.b = %d.\n", a.b); //a.b = 5555. func4(a); //sizeof(a1) = 8. //&a1 = 0x7ffc93bee400. //a1.b = 5555. return 0; }
结构体指针传参:
struct A { char a; // 结构体变量对齐问题 int b; }; void func5(struct A *a1) { printf("sizeof(a1) = %d.\n", sizeof(a1)); printf("sizeof(*a1) = %d.\n", sizeof(*a1)); printf("&a1 = %p.\n", &a1); printf("a1 = %p.\n", a1); printf("a1->b = %d.\n", a1->b); } int main(void) { struct A a = { .a = 4, .b = 5555, }; printf("sizeof(a) = %d.\n", sizeof(a)); //sizeof(a) = 8. printf("&a = %p.\n", &a); //&a = 0x7fffdde01030. printf("a.b = %d.\n", a.b); //a.b = 5555. func5(&a); // sizeof(a1) = 8. sizeof(*a1) = 8. //&a1 = 0x7fffdde01018. //a1 = 0x7fffdde01030. a1->b = 5555. return 0; }
5、传值调用与传址调用
传值调用描述的是这样一种现象:x和y作为实参,自己并没有真身进入swap1函数内部,而只是拷贝了一份自己的副本(副本具有和自己一样的值,但是是不同的变量)进入子函数swap1,然后我们在子函数swap1中交换的实际是副本而不是x、y真身。所以在swap1内部确实是交换了,但是到外部的x和y根本没有受影响。
在swap2中x和y真的被改变了(但是x和y真身还是没有进入swap2函数内,而是swap2函数内部跑出来把外面的x和y真身改了)。实际上实参x和y永远无法真身进入子函数内部(进去的只能是一份拷贝),但是在swap2我们把x和y的地址传进去给子函数了,于是乎在子函数内可以通过指针解引用方式从函数内部访问到外部的x和y真身,从而改变x和y。
结论:这个世界上根本没有传值和传址这两种方式,C语言本身函数调用时一直是传值的,只不过传的值可以是变量名,也可以是变量的指针。
void swap1(int a, int b) { int tmp; tmp = a; a = b; b = tmp; printf("in swap1, a = %d, b = %d.\n", a, b); } void swap2(int *a, int *b) { int tmp; tmp = *a; *a = *b; *b = tmp; printf("in swap1, *a = %d, *b = %d.\n", *a, *b); } int main(void) { int x = 3, y = 5; swap2(&x, &y); printf("x = %d, y = %d.\n", x, y); // 交换成功 int x = 3, y = 5; swap1(x, y); printf("x = %d, y = %d.\n", x, y); // x=3,y=5,交换失败 return 0; }
1、函数为什么需要形参与返回值
int x; // 被乘5的变量,也就是输入函数的变量 int y; // 输出结果的变量 void multip5_2(void) { y = 5 * x; } int multip5(int a) { return a*5; } int main(void) { // 程序要完成功能是:对一个数乘以5 // 第一种方法:函数传参 int a = 3; int b; b = multip5(a); printf("result = %d.\n", b); //result =15 // 第二种方法:用全局变量来传参 x = 2; multip5_2(); printf("y = %d.\n", y); //y =10 return 0; }
2、函数传参中使用const指针
void func1(int *p) { *p = 5; } void func2(const int *p) { *p = 5; } int main(void) { int a = 1; func1(&a); //ok func2(&a); //报错,func2中,指针指向的内容是const的不能被改变 return 0; }
3、函数需要向外部返回多个值时怎么办?
int multip5_3(int a, int *p) { int tmp; tmp = 5 * a; if (tmp > 100) { return -1; } else { *p = tmp; return 0; } } int main(void) { int a, b = 0, ret = -1; a = 30; ret = multip5_3(a, &b); if (ret == -1) { printf("出错了\n"); } else { printf("result = %d.\n", b); } return 0; }
总结:
譬如C库函数中strcpy函数
#include <stdio.h> int mystrncpy(char dest[],const char src[]) // dest输出型参数,src输入型参数加const { int i; for (i = 0; src[i] != '\0'; i++) { dest[i] = src[i]; } dest[i] = '\0'; return 0; } int main(int argc, char const *argv[]) { char a[5] = {0}; const char q [5] = "linux"; mystrncpy(a,q); printf("dest = %s\n",a); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。