C语言指针

一. 指针:

   1. 概念: 指针就是地址.

   2. 指针占4个字节(因为任何地址都是十六进制的, 类似0012FF5C, 每个字符占4个位, 一共8个字符=4个字节).

   3. 在书中或程序中的指针一般指的都是指针变量(简称).

二. 指针变量:

   1. 概念: 指针变量是用来存放另一个变量的内存地址的变量.

           如果变量A存放着变量B的地址, 我们可以说变量A指向变量B.

   2. 定义: 定义指针的目的是为了要通过指针间接地去访问变量的值.

      格式: 数据类型  *指针变量名 ;

           数据类型*  指针变量名 ;

      例: int*p ;   int*p ;

        (1). 一个类型的指针变量只能指向同类型的变量, 不能指向其他类型的变量.

       (2).在定义指针时, “*”号只能代表一个指针.

           ※ (1). int *a, b;         // 会有歧义. 不能这么写.

                  int *a, *b;      //正确, 但不建议.

                        int *a ; int *b ;  //建议写法.

                 (2). int*p ; →p是指针, 指向一个整型.

                        int* p ; →p是变量, 存的是int*.

                        int(*p)[10] ; →*p是指针, 存的是10个元素的数组, 元素类型为int.

                 类推: void (*p)(int) ;→p是无返回值的类型为int的函数.

                                  int* p[10] ; →p是存了10个元素的数组, 元素类型为int*.

      (3). 定义完后(int *p = &a ;), 如果看到下面的代码中有”*”号的如”*p”的就表示访问( *p = 100 ;), 意思是把

              p指向的变量a的值替换成100, 等价于( a = 100 ; ). 例:

             int a = 10;

              int *p = &a;

              int b = *p+3; 等同于  int b = a+3;

              *p += 6 ;    等同于  a += 6 ;

3. 指针变量的初始化:

      格式: 数据类型  *指针变量名 = &变量名;

规范的命名格式为:

           int nLength = 0 ;

             int *pnLength = &nLength ;

(1). 未初始化前的值不确定, 不能使用, 否则造成崩溃.

        (2). 指针变量的值可以改变, 即指向可以改变.

        (3). 指针不能乱指向, 会造成访问出错, 一般都指向首地址.

        (4). 如果一个指针在定义后没有初始化, 可以给它赋一个空值: int *p = NULL;

       (5).如果清楚变量的地址, 可以直接定义地址, 例:

             int a = 10;

             int *p = &a;   // 输出结果为a的地址: 0012FF5C

          可以写成

             int *p = (( int* )0x0012FF5C ) ;

   4. 赋值: 可以直接把一个变量的地址赋值给一个指针变量, 如:

       int a = 10 ;

        int b = 20;

        int *p = &a;

        p = &b ;

(1). 指针赋值时, 不能在指针变量前面加”*”.

      (2). 不允许把一个整型数赋值给指针变量, 因为指针变量的值不是一个整型数, 而是地址.

          p = 1000 ;  // 错误!

             p = 0x0012FF5C ;  // 错误!

              p = &1000 ;        // 错误!

          p = ( int* )0x0012FF5C ;   // 编译不会报错, 但运行结果未明, 除非自己很清楚指向的对象, 才不会崩溃(不建议使用).

   5. 类型匹配:

      如果赋值或初始化时被赋值的类型与指针变量的类型不匹配, 编译会报错. (void指针除外)

三. 间接引用指针:

   1. 当指针被赋值后, 可以通过指针来间接访问被指向的变量. 如:

     int a = 10 ;

     int *p = &a ;

     printf( “&a = %p\n”, &a ) ;  <==> printf( “p = %p\n”, p ) ;

   2. void的指针可以指向任何的变量(const对象除外), 需要进行强转后才能使用, 如:

     int a = 10 ;

       void *p = &a;    // 定义.

       *p = 100 ;           // 错误!

       *( (int*) p) = 100 ;   // 正确, 强转.

       printf( “%d\n”,  *( (int*)p) ) ;  // 相当于 printf( “%d\n”,a ) ;

     以上的代码等同于

     int *b = (int*)p;

       *b = 100 ; 

指针的运算

一. 指针的加减法:

   1. 指针与整数的加减法:

        法则如下:

pnID ± n  <==>  pnID ± n*sizeof(指针类型);

    例: int a[3] = {10,20, 30} ;

         int *p = &a[0];

         p = p+1 ; <==>  p = p+1*sizeof(int) ;  // sizeof()括号内的类型和数组类型一致.

         printf( “%d\n”,*p ) ;      // 结果为20, p指向了20.

        如果没有p = p+1 ;

       printf( “%d\n”, *(p+1) ) ;  // 结果也为20, 但p还是指向10.

※ *p是一个整型的指针, 加1后会使指针指向第2个元素, 而*p的地址实际上增加了4.

理解: p = p+1*sizeof(long) ;

       p = p+1*sizeof(char ) ;

   2. 指针之间的运算:

        法则如下:

        pnID-pnID1 <==>  (pnID-pnID1)/sizeof(指针类型);

例: int a[3] = { 10, 20, 30 } ;

   int *p = &a[0] ;

   int *q = &a[2] ;

   q-p ;   // printf结果为2.

   p-q ;  // printf结果为 -2.

   意义: 看q跟p之间有多少个元素, 一般为大的减小的. 正负无意义.

   3. 指针之间的比较运算(地址值的比较):

       p = &a[0];  // 设a[0]的地址为1000.

        q = &a[2] ;  // 则a[2]的地址为1012.

     (q>p)表达式为真.

二. 指针与一维数组:

   1. 用数组名访问数组元素:

      数组名保存的就是数组的首地址, 例:

      int a[3] = { 10, 20, 30 } ;

        int *p = &a[0];  <==>  int *p = a ;

※ a是数组, a的值为数组a的第一个元素.

*p是指向变量的指针.

     如何理解: 如果a是一个数组, 第一个元素的地址可以表示为&a[0]或简单表示为a. 相应地, 第二个元素的地址

                    可以写成&a[1]或者(a+1). 第(i+1)个元素地址可以表示为&a[i]或(a+i). 因此, 一个数组元素的地址

可以使用两种方式表示.

   2. 用指向数组的指针访问数组元素:
       将一个数组的地址赋值给一个指针, 让指针指向这个数组, 其实就是将这个数组的首地址赋值给指针. 如:

      int a[3] = {10, 20, 30} ;  

        int *p = NULL;

        p = &a[0] ;  <==> p = a ;

三. 通过指针引用数组元素:

   1. 如果p的初始值为&a[0], 则:

(1). a[i] <==> p[i] <==> *(a+i) <==> *(p+i)   // 取a[i]的内容, 和数组的运用一样.

        (2). &a[i] <==> &p[i]  // 访问第i个元素.

        (3). (a+i) <==> (p+i)  // 取a[i]的地址.

数组与指针的区别

       (4). ++p <==> (p+1)  // p可以做增量.

             ++a ;   // 错误, 不能对数组名做可以改变其值的运算.

      所以, 引用一个数组元素可以用:

      (1). 下标法:  a[i]

      (2). 指针法: *(a+i)  <==> *(p+i)   // a是数组名, p是指向数组的变量, p = a.

 不能用指针来求数组的元素个数(未知数组长度的情况下), sizeof行不通, 因为结果永远为4.      

四. 指针与函数:

   1. 指针变量作为函数参数:

      格式:

        void f( int a[], int_Length )  // 此条件等同于:  voidf(int *a, int_Length)

        {

           intb[100] = {........} ;

           f(b, 100 ) ;

}

※ (1). int a[] 是指针 <==> int *a 

(2). inta[1000]和int a[]一样, []里写多少都没影响, sizeof的结果都为4, 所以一般不填.

   3. 指针型函数: 返回值为指针类型的函数叫指针型函数.

        格式: 返回值类型* 函数名(形参表)

               {

                      .....  // 函数体.

}

字符指针和字符串指针

一. 字符串的表示形式:

   1. 字符数组表示字符串:

   2. 用字符串指针表示字符串:

   对比以下两种情况:

      (1). 注意指针与数组的区别:

char *p = ”ABCD” ;

             char a[100]= ”ABCD” ;

             p = “ABCDE” ;    

            a = ”ABCD” ;    // 报错!

             strcpy( a, “ABCDE” ) ;  // 正确!

             printf(“%p\n”, p ) ; <==> printf( “%p\n”,“ABCDE” ) ;  // 输出结果都为”ABCDE”.

          strcpy( a, “ABCDE” ) ;   // 正确.

             strcpy( p, “ABCDE” ) ;    // 崩溃. p指向的是数据区, 要修改数据区必然崩溃.

           总结: 指针改变指向的时候可以直接用等号赋值, 而数组不行, 必须用strcpy才可以.

       (2).char szBuffer[100] = ”” ;

                char * p = szBuffer ;

                 strcpy(p, “12345” ) ;  // 正确!! 等于是给szBuffer赋值.

                 puts(p);   

      总结: 数组a数据存在栈区, 可以被修改, 但p指向的字符串是存在数据区, 为常量, 不可被修改, 指针只要涉及到修改常量的内容的代码,

               运行结果一定崩溃.

        如何判断: 要看指针指向的是什么, 第一种情况指向的是常量, 所以不能改; 第二种情况指向的是数组, 数组的值可以更改, 所以正确.

      注意: 使用数组名的时候, printf( “%s\n”,a )的意义就是打印a的首地址为开头往后的字符, 到’\0’结束, 所以当char * p = &a或“ABC”

的时候, p保存的是a的首地址, printf( “%\n”,p )就是打印p指向的首地址开头往后的字符, 到’\0’结束.

二. 二重指针:

   指向指针的指针, 就叫二重指针. 格式为int** .

三. 杂项:

   1. 保存数组有以下几种方式:

        (1). char []     // C语言

       (2).char *    // C语言

        (3). string     // C++

      (4).CString   // MFC

   2. char *str = ”” ;  

        存的是’\0’的首地址.

   3.数组要赋值只能用strcpy写, 不能直接用等号

指向函数的指针和指向数组的指针

一. 指向函数的指针:

1. 格式:

        (1):

void f()

              {

              ...;

}

              void (*p)() = &f;     // 回调函数: 由自己实现, 由系统调用, 如鼠标点击右键.

        (2):

             int h( int _a, int _b )

              {

                  ...;

              }

              int (*p)( int, int ) = &h ;    // 括号里的是(int), 不能写(int _a, int_b).  

            <==> int (*p)( int, int ) = h ;  // 也对, 没那么标准.

       (3).void ( *p[5] )() = { f, f2 } ; 

             意义: p是个数组, *p是指针数组, 指向的是void函数.

             定义的方法: 和数组一样.

int  f( inta)

{

   return 1 ;

}

            void *p = &f ;

             ( ( int (*)(int ) )p ) (5);  // 使用方式: 强转

           详细: int(*) 为强转, 匹配f的类型, ( int )为参数a的类型, (5)为给a传值.          

 

二. 指向数组的指针:

1. 格式: inta[10] = {1, 2, 3……} ;

       int(*p)[10] = &a ;   // 写a会报错 (如果编译可以过的话, 就理解为等号两边是同类型的).

       p[0] = 10 ;         // 报错.

 ※ 和int *p = a ; 对比理解.

2. 调用: p[0][1] = 20 ;<==> (*p)[1] = 20 ; 

        特别注意p不是二重数组.

对比: int a1[10] = {...} ;

          int* b = a1 ;

          b[0] = 100 ;

注意: p[0]和b[0]访问的都是当前元素的第一个元素, 因为b[0]指向的是变量a1的首地址即a1[0], 而p[0]指向的是整个数组a,

当出现p+1和b+1的时候,  再次使用p[0]和b[0]的时候, b[0]为a1的第二元素a1[1], 而p[0]则为垃圾值. 因为p+1跳

过的不是一个元素, 而是整个数组.

     3. 指向二维数组的指针:

          (1). 格式: int (*p)[5][3] = &a ;

           (2).对比以下代码:

                 int a[5][3] = {...} ;                                             int a[10] = {...} ;

                 int (*p)[5][3] = &a ;                                            int*p = a ; <==> a[0]

                 p[0]= a ;        // int(*)[3] <==> &a[0]               int (*)[10] = &a ;

                 int [3] <==> a[0]                                               int [10] <==> a

                 p[0][2][1]= 100 ;  // 不是三维数组, 只是访问方式

           (3).sizeof( int*(*a[3])[4] ) ;   // 结果为12, 因为a是个数组, 存的是指针(指向数组的指针).

 

三. const指针:

   总纲: ①. const对象的地址只能赋值给const对象的指针.

②. 指向const的指针常被用作函数的形参(不能通过修改形参去改变实参的值).

③. 任何非const数据类型的指针都可以被赋值给void*类型的指针.

④. const数据类型的指针只能赋值给const void*型的指针.

          const int a = 10;

        const void* p =&a ;

⑤. void*型的指针被称为泛型generic指针, 它可以指向任意数据类型的指针(const对象除外).

   1. const char *p<==> char const *p

        定义: p is a pointer to const char.

        (1). p是指针(变量), 值可以改变.    

        (2). p可以指向的数据:

①. p可以指向const对象:

const int SIZE = 100 ;    

                 const int *p = &SIZE ;  

                 &p = 200 ;       // 错误!

             ②. p可以指向非const对象:

        (3). 无论const对象是什么类型, 它本身是否可以更改, 从p角度都不能改const对象的值(编译报错).

   2. char* const p:

        定义: p is a const pointer to char.

        (1). p必须初始化; p不能改变它的值(不能改变p的指向); p是const指针.

        (2). p可以指向的数据:

①. p可指向非const对象:

                 int a = 0 ;

                 *p = 100 ; // 正确.

                 int* const p = &a ;

                 int b = 20 ;

                 p = &b ;  // 错误!

             ②. p不能指向const对象:

                  const int SIZE = 100 ;

   3. const char* const p(前两种情况的合成)                  int* const p = &SIZE ;   // 错误!!

(3). 从p角度可以修改const对象的值.        a                     p

       定义: p is a const pointer to constchar .

       (1). p是const值, 不可改变, 必须初始化.

       (2). p可以指向的数据:

①. p可指向非const对象.

②. p也可指向const对象.




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值