一. 指针:
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对象.