指针(c的关键)
内存是操作系统对物理存储器的抽象-虚拟存储器。
虚拟存储器可以看成是一个个连续的小方格,每个方格的大小是一个字节(byte) = 8 bit,可以存放一个八位的二进制数,每个小方格都会1有一个编号,2gb的虚拟存储器 大小就是2*1024^3 byte 编号的范围是0~1024^3 -1
指针大概分析:指针是一个变量,他的值是可以变得,他里面存的是一个地址假设我们定义一个指针 int p; int a;如果p = &a;这个时候我们知道,a是一个变量,那么内存必定要给他分配一个地址来存储他,p指向a的意思就是p里面存的就是a的地址。所以p就是一个存着地址的变量。既然我们知道p里面存着a的地址,那么我们要找a就非常方便了,p就表示p存的地址里面的值,也就是a的值。这句话怎么理解呢?
~.p存的是地址,比作你住的地方,那么p就表示你了,因为p就表示这个地址里面的数据。这个时候p和a是完全一样的了,假如你要改变a的值。a = a+1和p =p+1是完全一样的,都能达到对a进行操作的目的。但是p =p+1和p =(p+1)是不一样的,这个用的时候要特别注意,因为p里面是地址,那么(p+1)就表示这个地址加1后,地址加1那不就是换了一个地址吗?换了一个地址后里面存的就肯定不是a了,就像可能是你的邻居了。因为地址变了,所以就是p变了,因此*也变了,这个地方有点难理解,楼主多琢磨琢磨。总结一句话:指针是一个万能钥匙,可以指向任何一个地址,可以改变任何一个地址里面的数据(只读的除外),因此使用指针要注意安全,以免发生异常。
1、运算符&
scanf("%d",&i); //这里的&是一个运算符
·作用:取得变量的地址,它的操作数必须是变量。
· 在C语言中,sizeof() 是一个判断数据类型或者表达式长度的运算符
·int i;printf("%x",&i);
`int i;printf("%p",&i); // 地址输出用%p 地址的大小和int取决于编译器 引申: %X的意思是以[十六进制](https://www.baidu.com/s?wd=十六进制&tn=SE_PcZhidaonwhc_ngpagmjz&rsv_dl=gh_pc_zhidao)数形式输出整数
1.%c:读入一个字符
2.%d:十进制整数
3.%f,%F,%e,%E,%g,%G 用来输入实数,可以用小数形式或指数形式输入
4.%o:八进制数
5.%s:读入一个字符串,遇空格、制表符或换行符结束。
6.%u:无符号十进制数
7.%%:输出百分号%
8.%i读入十进制,八进制,十六进制整数
9.%p读入一个指针
10.%n至此已读入值的等价字符数
11.%[]扫描字符集合
12.%a,%A读入一个浮点值(仅C99有效)
&不能对没有地址的东西取地址 先进后出,自顶向下 堆栈
a、指针就是一个保存地址的变量
int i;
int * p = &i; *:表示p是指针,指向int,把i的地址交给p *p表示i的值。 p里的值指向i那个变量的地址,此时说p指向了i。
int * p,q; int *p,q; // *p是一个int,q不是指针。 不管*和int靠得近还是和p靠得近。int *p,*q;此时p,q就都是指针了。
p是int型的数,p是*p的指针。
·p是一个指针变量的名字,表示此指针变量指向的内存地址,如果使用%p来输出的话,它将是一个16进制数
· *p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量
· &是取地址运算符,&p就是取指针p的地址
b、变量的值就是内存的地址。
·普通变量的值是实际的值。
·指针变量的值是具有实际值的变量的地址。
c、
void f(int *p) ; f需要int的指针 //在被调用的时候得到了某个变量的地址。
int i=0;f(&i) ; 当调用这个函数的时候应该交给它一个地址 用&取得变量的地址,然后将这个变量传给这个指针。 //在函数里可以通过这个指针访问外面的这个i。
#include <stdio.h>
void f(int *p);
void g(int k);
int main(void)
{
int i = 6;
printf("&i=%p
", &i); //&i:地址值&i被传进了函数f中
f(&i);
g(&i);
return 0;
}
void f(int *p) //p:通过这个地址
{
printf(" p=%p
", p);
printf("*p=%d
", *p);
*p = 26; //*p:用*p这种方式访问到i变量
} //指针就是保存了内存的一个地址(门牌号),通过这个地址就能够访问对于内存中的数据
void g(int k)
{
printf("k=%d
", k) //***p就是i=6的值,而p=&i,所以p和&i都是地址。 *地址=值 注:*p可以是和指针类型相同的变量也可以是常量**
} //p是i的地址,*p就是i(的值)。
d、
*是一个单目运算符,·在定义的时候只是说明是指针变量
·取用这个指针指向的地址所存储的变量
·用来访问指针地址的值,所表示的地址上的变量。
·可以是左值也可以是右值。
·int k=*p;
*p=k+1;
d、左值
赋值号左边不是变量,而是值,是表达式计算的结果。
a[0] =2; *p =3; 这两个左边的都不是变量。
e、反作用
*&yptr -> (&yper) -> (yper的地址)-> yper 两者反作用,就是表示原来的那个变量 &yper -> &(yper) -> &(y) ->得到y的地址 ->yper
f、传入地址
· i=6; int i;scanf("%d", i); 这样子输入编译是不会报错的,而运行则一定会出错,在32位中,整数和这个地址一样大 编译器误以为传入的是i的地址,实际上传进的是i的值6。
g、指针的实际应用
h、交换两个变量的值
#include <stdio.h>
void swap(int *pa, int *pb);
int main()
{
int a = 5;
int b = 6;
swap(&a,&b); //将a b的地址传递过awap函数中
printf("a=%d b=%d
", a, b);
return 0;
}
void swap(int *pa, int *pb) //参数是两个指针,如果不用指针,那么从main函数传来的就是值,而不是变量。
{
int t=*pa; //*pa取出pa所代表的变量,赋值给t
*pa=*pb;
*pb= t;
}
II、指针应用二
a、
·函数返回运算的状态,结果通过指针返回
·传入的参数实际上是需要保存带回的结果的变量
#include <stdio.h>
void minmax(int a[], int len, int *min, int *max); //len表示数组的大小
int main(void)
{
int a[] = {1,2,3,4,6,7,9,5,75,};
int min,max;
minmax(&a,sizeof(a)/sizeof(a[0]), &min, &max); //将&max,&min传递给minmax函数,sizeof(a)/sizeof(a[0])得到的是数组的个数,传给len a:也可以写作&a[0],表示a数组首元素的地址。
printf("min =%d,max=%d
", min, max);
return 0;
}
void minmax(int a[], int len, int *min, int *max)
{
int i;
*min = *max=a[0]; //对*min *max 赋初始值
for(i=0; i<len; i++ )
{
if(a[i] < *min) //遍历数组,发现数组中的某个元素比min小,就将该值赋给min
{
*min = a[i];
}
if(a[i] > *max) //同理,元素比max大就赋给max
{
*max = a[i];
}
}
}
b、
函数返回运算的状态,结果通过指针返回
#include <stdio.h>
//** @return 如果除法成功,返回1,否则就返回0.
int divide(int a, int b, int *result);
int main(void)
{
int a=4;
int b=2;
int c;
if( divide(a, b, &c) )
{
printf("%d/%d=%d
", a, b, c);
}
return 0;
}
int divide(int a, int b, int *result)
{
int ret = 1;
if( b == 0 ) ret = 0;
else {
*result = a/b;
}
return ret;
}
h、指针的常见错误
·定义指针变量,还没有指向任何变量就开始使用指针。
·任何一个地址变量没有被赋值之前,没有得到实际的变量之前不能用*访问数据。
i、函数与指针
int isprime(int x, int knowPrimes[], int nuberofKnowPrimes)
{
int ret =1;
int i;
for ( i=0; i<nuberofKnowPrimes; i++ )
{
if( x %nuberofKnowPrimes[i] == 0 )
{
ret = 0;
break;
}
}
return ret;
}
·函数参数表中的数组实际上是指针。
·sizeof(a) == sizeof(int *)。
·但是可以用数组的运算符[]进行运算。
I、函数参数
int sum(int *ar, int n);=>等价于int sum(int ar[],int n);
int sum(int *,int );=>int sum(int [], int);
j、说明
数组变量是特殊的指针。是const 指针(常量指针)
·数组变量本身表达地址,所以
·int a[10];int *p=a; //无需用&取地址。
·但是数组的单元表达的是变量,需要用&取地址。
eg:a == &a[1]
[]运算符可以对数组做也可以对指针做。
·p[0]<==>a[0]
*运算符可以对指针做,也可以对数组做。
&a = 25
指针有多个变量,*a == a[0]
·数组变量是const的指针,所以不能被赋值
int a[]<==> int *const a=...
k、指针与const
如果指针是const,
·表示一旦得到某个变量的地址,就不能再指向其他的变量了
·int *const q = &i //q是const。q的值(i的地址)不能被改变。
·*q = 26; // OK
·q++;//ERROR 不能做了
所指的是const
·表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
·const int *p = &i;
·*p = 26; //ERROR! ( *p)是 const
·i = 26; //OK
·p = &j; //OK
~总结int *const不能改变地址,可以改变值
const *int 不能改变值,可以改变地址。
分析:
int i;
const int* p1 = &i;//值不能修改
int const* p2 = &i;//值不能修改
int *const p3 = &i;//指针不能改
const在*的前面表示 所指的东西(值)不能被修改,coust在后面则表示指针不能被修改
记忆小技巧:const在前面,只读指针,const在后面,定向读写指针。
const数组
·const int a[]={1,2,3,4,5,6,};
·因为数组可以看成是const指针,数组变量就已经是const了,zhelideconst数组表示数组的每一个单元都是coust int
·所以必须初始化进行赋值。
保护数组值
·把数组传入函数是传递的是地址,所以那个函数内部可以修改数组的值。
·为了保护数组不被破环,可以直接设置const 这样子就变成只读了。
·eg:int sum(const int a [], int length);
这样子sum函数就不会对a[]进行修改。
l、
I、指针的运算:
指针+1 是指加一个单元,而不是一个字节。
eg:int一个单元是4个字节 int型指针+1 就是加4个字节
#include <stdio.h>
int main(void)
{
char ac[] = {1,2,4,5,7,};
char *p = ac; //不太懂这行的意思
printf("p =%p
", p);
printf("p+1 =%p
", p+1); //指针加1就是加上了一个单元而不是一个字节。 例:int 型+1就是 +4个字节
printf("*p =%d
", *p);
printf("*(p+1)=%d
", *(p+1));
int ai[] = {1,2,4,5,7,};
int *q = ai;
printf("q =%p
", q);
printf("q+1 =%p
", q+1);
printf("*q =%d
", *q);
printf("*(q+1)=%d
", *(q+1));
return 0;
}
·指针加,减一个整数(+,+=,-,-=)
·递增递减(++/--)
·两个指针相减。
eg:两个指针相减
int *p = &ac[0]; //或者写为ac即可。
int *p1= &ac[6];
结果:p1-p=6 将p1和p的指针地址 (16进制地址)相减 最后转换为10进制后,就是24 两个指针相减得到的不是两个地址的差24。
为什么这里是6呢 就是24除sizeof =>得到的是有几个这样类型的东西在。
指针加减=地址加减/sizeof(字节类型)
··p++ ++优先级高于 * 所以就不需要括号 这里的运算规则是:先加1,再取。
*p++常用于数组类的连续空间操作。
指针的类型
·无论指向什么类型,所有的指针的大小都是一样的,因为都是地址。
·但是指向不同类型的指针是不能直接相互赋值的。(为了避免误用指针)
强制转换指针类型 //不要轻易做
·void*表示不知道指向什么东西的指针。
·计算时与char*相同(但不相通)
·指针也可以转换类型
eg:*int p = &i; void *q= (void *)p; //void * 表示 不知道什么类型。 //不太懂 黑体中代表的意思。
i还是int,但从q上看就是void。
零地址
内存中有零地址,但零地址不要轻易去触碰。
零地址一般是用来表示特殊的事情。
NULL是一个预定定义的符号,表示零地址。
m、动态内存分配
malloc函数:
#include <stdio.h>
#include <stdlib.h> //用malloc必备条件。
int main(void)
{
int number;
int* a;
printf("输入数量:");
scanf("%d", &number);
// int a[number];
a =(int*)malloc(number*sizeof(int)); //用malloc
int i;
for (i=0; i<number; i++)
{
scanf("%d", &a[i]);
}
for (i=number-1; i>=0; i-- )
{
printf("%d",a[i]);
}
free(a);
return 0;
}
头文件:#include <stdlib.h>
void* malloc(size_t size);
参数是siae_t 返回类型是void size
向malloc申请的空间得大小是以字节为单位的。
返回结果是void* ,需要转换类型为自己所需的。
int* malloc( n*sizeof(int/char/double))
free();还(huan)掉内存,把申请来的地址还给系统,只能退还申请来的空间的首地址。
看电脑能分配多少内存
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
void *p;
int cnt = 0;
while( (p=malloc(100*1024*1024)) ) //先将malloc赋给p,然后将p得到的值作为while的条件。
{
cnt++;
}
printf("分配了%d00MB的空间
", cnt);
return 0;
}
关于空指针NULL、野指针、通用指针
首先说一下什么是指针,只要明白了指针的含义,你就明白null的含义了。
假设 有语句 int a=10;
那么编译器就在内存中开辟1个整型单元存放变量a,我们假设这个整型单元在内存中的地址是 0x1000;那么内存0x1000单元中存放了数据10,每次我们访问a的时候,实际上都是访问的0x1000单元中的10.
现在定义:int *p;
p=&a;
当编译器遇到语句int *p时,它也会在内存中给指针变量p分配一个内存单元,假设这个单元在内存的编址为0x1003;此时,0x1003中的值是不确定的,(因为我们没有给指针赋值),当编译器遇到了p=&a时,就会在0x1003单元中保存0x1000,请看,这就是说:(指针变量p代表的)内存单元0x1003存放了变量a的内存地址!用通俗的话说就是p指向了变量a。
p=NULL,就是说:内存单元0x1003不存放任何变量的内存地址。
删除一个new了的数组。有必要的话。比如非标准的类( new CMyClass),在Type *p = new Type[N]; delete []p;的最后最好再加一句: p = NULL
空指针是一个特殊的指针值,也是唯一一个对任何指针类型都合法的指针值。指针变量具有空指针值,表示它当时处于闲置状态,没有指向有意义的东西。空指针用0表示,C语言保证这个值不会是任何对象的地址。给指针值赋零则使它不再指向任何有意义的东西。为了提高程序的可读性,标准库定义了一个与0等价的符号常量NULL. 程序里可以写 p = 0; 或者 p = NULL; 两种写法都把p置为空指针值。相对而言,前一种写法更容易使读程序的人意识到这里是一个指针赋值。
我们印象中C语言的指针都有类型,实际上也存在一种例外。这里涉及到通用指针,它可以指向任何类型的变量。通用指针的类型用(void *)表示,因此也称为void 指针。
int n=3, *p;
void *gp;
gp = &n;
p=(int *)gp1;1234
野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。
“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。野指针的成因主要有两种:
一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。例:
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
if(p != NULL) // 没有起到防错作用
strcpy(p, “world”); // 出错123456789
另外一个要注意的问题:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
指针是个很强大的工具,可是正因为它太强大,所以要操作它不是件易事。操作不当造成的野指针,甚至会引起系统死机等比较严重的后果。
如果程序定义了一个指针,就必须要立即让它指向一个我们设定的空间或者把它设为NULL,如果没有这么做,那么这个指针里的内容是不可预知的,即不知道它指向内存中的哪个空间(即野指针),它有可能指向的是一个空白的内存区域,可能指向的是已经受保护的区域,甚至可能指向系统的关键内存,如果是那样就糟了,也许我们后面不小心对指针进行操作就有可能让系统出现紊乱,死机了。所以我们必须设定一个空间让指针指向它,或者把指针设为NULL,这是怎么样的一个原理呢,如果是建立一个与指针相同类型的空间,实际上是在内存中的空白区域中开辟了这么一个受保护的内存空间,然后用指针来指向它,那么指针里的地址就是这个受保护空间的地址了,而不是不可预知的啦,然后我们就可以通过指针对这个空间进行相应的操作了;如果我们把指针设为NULL,我们在头文件定义中的 #define NULL 0 可以知道,其实NULL就是表示0,那么我们让指针=NULL,实际上就是让指针=0,如此,指针里的地址(机器数)就被初始化为0了,而内存中地址为0 的内存空间……不用多说也能想象吧,这个地址是特定的,那么也就不是不可预知的在内存中乱指一气的野指针了。
还应该注意的是,free和delete只是把指针所指的内存给释放掉,但并没有把指针本身干掉。指针p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。用free或delete释放了内存之后,就应立即将指针设置为NULL,防止产生“野指针”。内存被释放了,并不表示指针会消亡或者成了NULL指针。(而且,指针消亡了,也并不表示它所指的内存会被自动释放。)
最后,总结一下野指针的的成因吧: 1、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。 2、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。 3、指针操作超越了变量的作用范围。这种情况让人防不胜防