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

  • 相关阅读:
    jQuery:balloon气泡提示插件
    等高布局
    html5 语音搜索
    JS编码三种方法的区别:escape、encodeURI和encodeURIComponent
    为什么要两次调用encodeURI来解决乱码问题
    关于时间差查询的一个小技巧
    MySQL对时间的处理总结
    闭包总结:从执行环境来看闭包和垃圾回收
    闭包总结:闭包的7种形式
    JavaScript里面向对象的继承:不使用构造函数实现"继承"
  • 原文地址:https://www.cnblogs.com/wpoem/p/12448655.html
Copyright © 2020-2023  润新知