指针:
存储:
字节是最小的存储单元。每一个内存单元有一个编号,这个编号就是地址。
指针:
指针就是地址。
指针变量:
指针在没有放入地址之前,叫地址;放入地址之后叫指针。
指针变量的定义:
(1)、类型:通过类型,我们知道从这个地址取出来的数据是什么类型的,也可以知道取出来多少个字节。
(2)、*:*号是一个标识作用,在定义的时候,告诉你这个变量是指针类型的。
(3)、NULL:指针变量的初值,表示空,没有指向任何内存。
通常我们再程序中,把指针变量叫做指针。
指针变量定义的形式:
int *p1 = NULL; |
定义一个char类型、double类型、long类型、float类型的指针变量。
// char *p2 = NULL; // double *p3 = NULL; // float *p4 = NULL; // long *p5 = NULL; |
区分一下:定义时的*号和取值符*号的区别,再定义一个指针时,*号表示的意思是,这个变量是指针变量;在定义完事之后,要取出这个指针变量所指向的内存所存储的值,就用*p来取值,此时的*号为取值符。
关于指针的一些符号:
& :表示取地址符,用来取一些变量的地址。
* :取值符。用来取对应地址所存储的内容。
%p:打印地址占位符。打印地址的时候,用%p来作为占位符。
一些例子:
// 获取num1的地址 // int num1 = 10; // // p1 = &num1; // 打印地址 p1 // printf("%p\n",p1);//0x7fff5fbff834
// 通过地址取值 // printf("%d\n",*p1);//10
|
注意:
不要直接给变量赋地址编号,虽然不报错,但是这是不合理的,系统会判断为你访问了不该访问的内存编号,在运行的时候,会因为内存出错而导致程序崩溃。
用上面的例子来说:
// int *pt = 0x1;//若把地址编号改成0x7fff5fbff834,则可以访问,因为这个地址编号,就是系统给你用的。 // printf("%d\n",*pt);//10 |
若把地址编号改成0x7fff5fbff835,即把pt + 1 也能使用,因为系统给你分的内存,不仅仅是一块,而是给你4块,若是在这4块之外,即pt + 5,则会直接导致结果直接崩掉。
指针的移动:(指针的算数运算,只有+ -)
指针可以移动,但是移动多少个字节,需要根据它所指的数据的类型来决定。
例如,它指向的数据是int型(占4个字节)的,那么指针在每一次移动的时候,是移动4个字节。
例子:
// for (int i = 0; i < 10; i ++) { p1没有移动。 // printf("%d\n",*(p1+i)); p1移动了。结束后,p1已经不指向原来的位置了。 // printf("%d\n",*(p1 ++));
// printf("%p\n",p1+i); /* 输出: 0x7fff5fbff854 0x7fff5fbff858 0x7fff5fbff85c 0x7fff5fbff860 0x7fff5fbff864 0x7fff5fbff868 0x7fff5fbff86c 0x7fff5fbff870 0x7fff5fbff874 0x7fff5fbff878 每次移动4个字节,从上到下,每次+4 */ // }
|
指针重指向:
指针重指向,即为给一个指针重新赋值。
// int num2 = 20; // //指针重指向:给指针变量再赋值。 // p1 = &num2; //p1指向了num2的地址。 |
指针与数组的关系:
数组:用连续的内存空间来存储数据的构造类型。
数组名本身就是整个数组的首地址,也就是第1个元素所在的地址,因此,再取地址时,不需要再用&取值符了。
//数组名就是整个数组的首地址 //数组名本身就是地址,不用再取地址了。 // int array[3] = {1,3,8}; // printf("%p\n",array);//0x7fff5fbff840 // 数组的首地址,就是数组中,第一个元素的地址 // printf("%p\n",&array[0]);//0x7fff5fbff840 |
一个指向int类型数组的指针,是int类型。在一个指针指向一个什么类型的数组时,这个指针对应的就是什么类型的。
使用指针和数组名取值:
(1)、用数组名取值
// 一个指向int类型数组的指针,是int类型 // int *pi = array; // printf("%d\n",*pi);//1 // printf("%d\n",*(pi+1));//3 //使用指针和数组名取值 //数组名取值。 // printf("%d\n",array[0]);//1
// printf("%d\n",*array);//1 // printf("%d\n",*(array + 2));//8 |
(2)、使用指针变量取值:
//使用指针变量取值 // printf("%d\n",pi[0]);//1 // printf("%d\n",pi[2]);//8 // printf("%d\n",*pi);//1 // printf("%d\n",*(pi+1));//3 |
注意的是:
指针在取值时,可以当做数组名来使用(即可以按照数组取值那样取值)。
p[ x ] = * ( p + x ) 是等价的。(x)表示任意一个整型数,p表示指针变量或者表示数组名。
数组名和指针的区别:数组名是地址常量,指针变量是变量。常量的值是固定的,不能再改变,指针变量的值是可以改的。
int array[3] = {1,3,8}; int *pi = array; // 数组名和指针的区别。 // 数组名是地址常量 // 指针变量是变量 // pi ++;//合法 // array ++;//报错 |
指针的内存空间:
指针本身占内存空间的大小,与它所指向的数据的类型无关,只与它所在的操作系统的位数有关。32位系统和64位系统的指针占内存不同。
例子:
// 内存空间 // printf("%lu\n",sizeof(array));//12 // printf("%lu\n",sizeof(pi));//8,指针本身的占内存空间的大小,不关其他类型的事。指针占内存的大小,只与操作系统的位数有关。 |
一个练习:
//给你一个数组:元素都是int类型的,但是你不知道有多少个元素。 // int array1[] = {1,2,3,4,5,6}; // // int arr_length = sizeof(array1); // printf("%lu",arr_length / sizeof(int));//6 |
注意的一些问题:
不同类型的指针,相互之间相互赋值,是不合法的,虽然可能可以取到正确的值,但是很多情况下,取值会出现问题。
具体来说,若定义了一个int型的数,然后定义一个char型的指针变量p,此时通过 p 来取值,则可以取到正确的值。原因是:int类型的数据,再内存中占有4个字节32位,即系统分配了4个连续的存储空间,每个空间1个字节(8位)给这个int型的数,但是此时这个数比较小,只在第一个存储空间就可以装完,则其他三个字节空间都为0。此时,char类型占一个字节,正好可以取到int型数据的第一个字节空间的值,也正好能存完。此时取到的值是正确的。(画图便能说明白)。若定义了short型的数据(占2个字节),分配了2个连续的内存空间,此时定义的指针是int型,则指针此时取值的时候,取到的是连续4个字节空间的值,此时,就会把这4个连续的内存空间的所有2进制数加起来变成一个很大的10进制数。那么取到的值就是错误的。
例子:
// int num1 = 3; // //不同类型指针,取值会出问题。 // char *pn = &num1; // // printf("%d\n",*pn);
// short a[4] = {3,7,9,1};//short类型占2个字节 // int *p1 = a; //int类型占4个字节 // char *p2 = a; //char类型占1个字节 // // printf("%d\n",*p1);//458755,因为p1取了4个字节共32位的所有0、1,计算结果即为458755。 // printf("%d\n",*p2);//3 ,因为p2只取了一个字节的值,这个字节里正好存了3,正好可以存进char,所以可以取到3。 |
指针和字符串的关系:
首先记住一个东西:栈区的值是可以改变的。常量是放在常量区的,常量区的所有值是不能改变的。那么就好办了。
字符指针可以操作单个字符,也可以操作字符串。
看例子就能明白了:
char string[] = “iPhone”; //string为数组名 char *p = string; //指向字符数组⾸地址 *(p + 1) = ‘v’; //操作单个字符 printf(“%s”,p + 1); //操作整个字符串
指针和字符串的关系 string1再栈区存放,栈区的值是可以改的。 // char string1[] = "Ipad"; // char *pc = string1; // printf("%s\n",pc);//Ipad 改变字符数组的位置 pc[0] = 'i'; // *(pc + 0) = 'i'; // // printf("%s\n",pc);//ipad //常亮字符串存在常量区,常量区的所有数据只读不可写(不能改变值) // char *ps = "Iphone"; // printf("%s\n",ps);//Iphone
// ps[0] = 'i'; // printf("%s\n",ps);//出错 // printf("%c\n",ps[0]);//I
// printf("%c%c%c",*(ps+3),*(ps+4),*(ps+5));//one // ps += 3; //%s要的是字符串的首地址和结束的标志"\0" // printf("%s",ps);//one // printf("%s",ps + 3);//one
|
一些练习:
// 练习: // 通过指针计算字符串的长度
char string2[] = "Iphone"; char *ps = string2; int n = 0; // while (ps[n] != '\0') { 或者 while (*(ps + n) != '\0') { n ++; } printf("%d\n",n);//6 |
指针数组:(跟那个指针与数组的关系区分一下)
指针数组就是一个数组,这个数据里的所有元素,都是指针(也就是地址,其实是每个字符串的首地址)。
注意:这些字符串,不是存在这个指针数组中,而是存在另外一个字符数组中,这个指针地址存的是那个字符数组的每一个字符串的首地址。
//指针数组
//指针数组是一个数组,数组中的元素,都是指针(地址,其实是每一个字符串的首地址)。 //注意:这些字符串,不存在指针数组中。 // char *strarray[3] = {"iOS","Android","Windows10"}; // // for (int i = 0; i < 3; i ++) { // printf("%s\n",strarray[i]);/* // iOS // Android // Windows10 // */ // } |
指针作为函数参数:
这个就是精华所在:指针作为参数,可以跨作用域取改值。
//指针作为参数。 //(重点):指针可以跨作用域改值。
int swap (int *n1,int *n2){ *n1 = *n1 ^ *n2; *n2 = *n1 ^ *n2; *n1 = *n1 ^ *n2; return 0; } //函数声明: int swap(int *num1,int *num2); //主函数 int main(int argc,constchar * argv[]) { // 例子:写一个函数 // 有两个参数,交换这两个参数 int num1 = 10; int num2 = 20; printf("交换前:\n"); printf("num1 = %d\nnum2 = %d\n",num1,num2); swap(&num1,&num2); printf("交换后:\n"); printf("num1 = %d,num2 = %d",num1,num2);//num1 = 20,num2 = 10 return 0; } |