收获一:
指针的定义与使用
1.指针的含义是什么?指针的长度是什么?
指针是一个变量,存放的是指向数据的地址,不同的数据类型具有不同的宽度。
在X86系统中指针占4个字节宽度,在X64系统中指针占8个字节宽。
2.假如p是指针*p是什么?sizeof§与sizeof(*p)的值分别是多少?
*p是解引用,是指针指向数据的数值。定义时 *p为指针,使用时与所指向变量等价。
sizeof§是指针的宽度,在X86系统中指针占4个字节宽度,在X64系统中指针占8个字节宽。sizeof(*p)是所指向类型的宽度,int *p 为四个字节宽度。
3.如何判断一个系统是低位优先还是高位优先?
Bool xitong(){
Int x=0x1;
Char *pi=(char *)&x;
If(pi==1)
Trturn truel;
}
4.分析下面程序的输出,为什么?
Int main(void)
{
Int num = 0x000000ff;
char *p1=(char*)#
Unsigned char *p2=(unsigned char *)#
Printf(“%lu\n”,*p1);
Printf(“%lu\n”,*p2);
Return 0;
}
收获二:
指针加减运算
1.p是一个整数指针,比较下面语句含义
p+1; //指针向右移一个整数类型的宽度
(char*)p+1;//强制转换为char*类型指针向右移动一个字节
(unsigned long)p+1;//强制转换为无符号的长整形数值加一
2.表达下面语句的含义
*p++;
// *(p++)->取值,位置+1后移
(*p)++;
//取值+1
*++p;
// *(++p)->位置+1后移,取值
++*p;
//++(*p)->取值+1
3.计算结果
Int a [10];
Int arr [10][20];
a+1/&a+1 //a后移4个字节/后移40个字节
arr+1/&arr+1 //arr后移80个字节/后移800个字节
收获三:
指针数组与数组指针
数组和指针可以转换使用
1.什么是指针数组?
Int *a[10];//存放指针的数组
2.什么是数组指针?
Int (*a)[10];//指向十个元素的一维数组的指针,或者是指向每行有十个元素的二维数组的指针
3.下面的定义分别是什么?
Int *a[10];指针数组
Int(*a)[10];数组指针
Int a[10];一维数组
收获四:
需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。
也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。
收获五:
1.int* p= &a;
2. * p=100;
第1行代码中用来指明 p 是一个指针变量,第2行代码中用来获取指针指向的数据然后赋值。
修改上面的语句:
1.int*p;
2.p=&a;
3.*p=100;
需要注意的是,给指针变量本身赋值时不能加 *。
收获六:
关于 * 和 & 的谜题
假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a 和&pa 分别是什么意思呢?
&a 可以理解为(&a),&a 表示取变量 a 的地址(等价于 pa),(&a)表示取这个地址上的数据(等价于 pa),绕来绕去,又回到了原点,&a 仍然等价于 a。
&*pa 可以理解为&(*pa),*pa 表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa 等价于 pa。
简单的来说*和&可以互消。
收获七:
对星号* 的总结
在我们目前所学到的语法中,星号*主要有三种用途:
1.表示乘法,例如 int a = 3, b = 5, c; c = a * b;,这是最容易理解的。
2.表示定义一个指针变量,以和普通变量区分开,例如 int a = 100; int *p = &a;。
3.表示获取指针指向的数据,是一种间接操作,例如 int a,b,*p=&a; *p=100; b= *p;。
收获八:
不要对指向普通变量的指针进行加减运算,只有数组的内存是连续的,否则会发生越界产生垃圾值。
另外需要说明的是,不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义
收获九:
定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在 C语言中,我们将第 0 个元素的地址称为数组的首地址。
收获十:
数组指针:arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。
sizeof ( p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
int *p = &arr[2]; //也可以写作 int *p = arr + 2;
使用下标
也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。
也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。
区别:
不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素。
易错点:
*p++ 应该理解为 *(p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的 。
收获十一:
关于数组指针的谜题
假设 p 是指向数组 arr 中第 n 个元素的指针,那么 * p++、*++p、(*p)++ 分别是什么意思呢?
*p++ 等价于 *(p++),表示先取得第 n 个元素的值,再将 p 指向下一个元素,上面已经进行了详细讲解。
*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。
(*p)++ 就非常简单了,会先取得第 n 个元素的值,再对该元素的值加 1。假设 p 指向第 0 个元素,并且第 0 个元素的值为 99,执行完该语句后,第 0 个元素的值就会变为 100。
如何进行理解,单目运算是从右向左,()>[]>++
收获十二:
字符数组与常量字符串:
它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。
内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。
我们将第二种形式的字符串称为字符串常量,意思很明显,常量只能读取不能写入。
指针可以更改指针变量本身的指向,不能修改字符串中的字符。
收获十三:
#include
int main(){
char str[20] = "c.biancheng.net";
char *s1 = str;
char *s2 = str+2;
char c1 = str[4];
char c2 = *str;
char c3 = *(str+4);
char c4 = *str+2;
char c5 = (str+1)[5];
int num1 = *str+2;
long num2 = (long)str;
long num3 = (long)(str+2);
printf(" s1 = %s\n", s1);
printf(" s2 = %s\n", s2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
printf(" c4 = %c\n", c4);
printf(" c5 = %c\n", c5);
printf("num1 = %d\n", num1);
printf("num2 = %ld\n", num2);
printf("num3 = %ld\n", num3);
return 0;
}
输出结果:
s1 = c.biancheng.net
s2 = biancheng.net
c1 = a
c2 = c
c3 = a
c4 = e
c5 = c
num1 = 101
num2 = 2686736
num3 = 2686738
数组元素的访问形式可以看做 address[offset],address 为起始地址,
offset 为偏移量:c1 = str[4]表示以地址 str 为起点,向后偏移 4 个字符
收获十四:
指针的指针
假设 a、p1、p2、p3 的地址分别是 0X00A0、0X1000、0X2000、0X3000,它们之间的关系可以用下图来描述:
收获十五:
C语言动态内存分配函数 malloc() 的返回值就是 void *类型,在使用时要进行强制类型转换,请看下面的例子:
1.#include
2.
3.int main(){
4. //分配可以保存30个字符的内存,并把返回的指针转换为char *
5. char *str = (char *)malloc(sizeof(char) * 30);
6. gets(str);
7. printf("%s\n", str);
8. return 0;
9.}
收获十六:
C 语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof 或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。
a[3] 等价于 *(a + 3),3[a] 等价于 *(3 + a),仅仅是把加法的两个操作数调换了位置。
关于数组和指针可交换性的总结
用 a[i] 这样的形式对数组进行访问总是会被编译器改写成(或者说解释为)像
*(a+i) 这样的指针形式。
指针始终是指针,它绝不可以改写成数组。你可以用下标形式访问指针,一般都是
指针作为函数参数时,而且你知道实际传递给函数的是一个数组。
在特定的环境中,也就是数组作为函数形参,也只有这种情况,一个数组可以看做
是一个指针。作为函数形参的数组始终会被编译器修改成指向数组第一个元素的指针。
当希望向函数传递数组时,可以把函数参数定义为数组形式(可以指定长度也可以
不指定长度),也可以定义为指针。不管哪种形式,在函数内部都要作为指针变量对待。
收获十七:
指针数组
#include
int main(){
char *str[3] = {
"c.biancheng.net",
"C 语言中文网",
"C Language"
};
printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
return 0;
}
需要注意的是,字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。
也只有当指针数组中每个元素的类型都是 char *时,才能像上面那样给指针数组赋值,其他类型不行。
指针数组
#include
int main(){
char *string0 = "COSC1283/1284";
char *string1 = "Programming";
char *string2 = "Techniques";
char *string3 = "is";
char *string4 = "great fun";
char *lines[5];
lines[0] = string0;
lines[1] = string1;
lines[2] = string2;
lines[3] = string3;
lines[4] = string4;
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
return 0;
}
lines[1]:它是一个指针,指向字符串 string1,即 string1 的首地址。
* (lines + 3):lines + 3 为数组中第 3 个元素的地址,* (lines + 3) 为第 3 个元素的值,它是一个指针,指向字符串 string3。
*( * (lines + 4) + 6): * (lines + 4) + 6 == lines[4] + 6 == string4 + 6,表示字符串 string4 中第 6 个字符的地址,即 f 的地址,所以 * ( * (lines + 4) + 6) 就表示字符 f。
( * lines + 5)[5]:lines + 5 为字符串 string0 中第 5 个字符的地址,即 2 的地址,( * lines + 5)[5]等价于(*lines + 5 + 5),表示第 10 个字符,即 2。
*lines[0] + 2:lines[0] 为字符串 string0 中第 0 个字符的地址,即 C 的地址;
*lines[0] 也就表示第 0 个字符,即字符 C。字符与整数运算,首先转换为该字符对应的 ASCII 码,然后再运算,所以 *lines[0] + 2 = 67 + 2 = 69,69 对应的字符为 E。
收获十八:
指针数组和二维数组指针的区别
指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在 32 位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
收获十九:
函数指针:
1. #include
2.
3. //返回两个数中较大的一个
4. int max(int a, int b){
5. return a>b ? a : b;
6. }
7.
8. int main(){
9. int x, y, maxval;
10. //定义函数指针
11. int (*pmax)(int, int) = max; //也可以写作 int (*pmax)(int a, int
b)
12. printf("Input two numbers:");
13. scanf("%d %d", &x, &y);
14. maxval = (*pmax)(x, y);
15. printf("Max value: %d\n", maxval);
16.
17. return 0;
18.}
收获二十:
对 C 语言指针的总结
指针变量可以进行加减运算,例如 p++、p+i、p-=i。指针变量的加减运算并不是简单
的加上或减去一个整数,而是跟指针指向的数据类型有关。
给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如 int
*p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。
使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内
存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值 NULL。
两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相
减的结果就是两个指针之间相差的元素个数。
数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或
者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为
一个指向数组的指针。
收获二十一:
1)int *a[10]; //指针数组,存放指针的数组
2)int (*a)[10]; //数组指针,指向是个元素的一维数组的指针,或者是指向每行又是个元素的二维数组的指针
3)int (*a)(int); //函数指针
int *a(int); //指针函数,返回指针的函数
4)int (*a[10])(int); //函数指针数组。注意:*与[]的优先级来判断这组的区别
5)int *a, **a; //指针和指向指针的指针
6)char str[]; //字符串数组
7)char *str, **str; //字符指针和指向字符的指针
要掌握这些复杂形式的指针意义并不容易,因为容易混淆。那么有没有特别的要领呢?其实此题的关键是要明白 [ ],* , 和 ( )运算符的优先级:( ) > [ ] > *。比如 int * a[10],由于[ ]的运算级别高于 *,所以该表达式首先是一个数组。那么它是什么数组呢?由int *确定它是个指针数组。又比如int ( * a)[],由于( ) 高于[ ],所以它是一个指针。那么它是什么指针呢?由 [ ]确定它是个数组指针,即指向数组的指针。
与“指针数组”和“数组指针”类似的有“函数指针”与“指针函数”,“常量指针”与“指针常量”。这些概念都符是偏正关系,所以指针数组其实就是数组,里面存放的是指针;数组指针就是指针,这个指针指向的是数组;函数指针就是指针,这个指针指向的是函数,指针函数就是函数,这个函数返回的是指针;常量指针就是指针,只不过这个指针是常量的,不能再修改值指向别的地方;指针常量,就是指指针本身不是常量指针指向的内存是常量,不能修改。
收获二十二:
数组名:数组的首地址,常量指针
Int a[10]
Int arr[10][20]
a:int *const a;//4
&a:int (*const a)[10];//40
arr:int (*const arr)[20];//80
&arr:int (*const arr)[10][20];//800
Scanf(“%d”,&a);//输入时不要写\n
数组做参数会退化为指针