听翁恺讲C语言8-指针

指针(c的关键

内存是操作系统对物理存储器的抽象-虚拟存储器。

虚拟存储器可以看成是一个个连续的小方格,每个方格的大小是一个字节(byte) = 8 bit,可以存放一个八位的二进制数,每个小方格都会1有一个编号,2gb的虚拟存储器 大小就是2*1024^3 byte 编号的范围是0~1024^3 -1

image-20200309143609430指针大概分析:指针是一个变量,他的值是可以变得,他里面存的是一个地址假设我们定义一个指针 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(的值)。

image-20200309143829064

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、指针操作超越了变量的作用范围。这种情况让人防不胜防

原文地址:https://www.cnblogs.com/wpoem/p/12448655.html