三、指针与字符串 指针 导入 我们从sizeof谈起:它是一个运算符``。给出某个类型或变量在内存中占据的位置,以字节大小表示(1字节=4比特) 接着是&,它实质上是一个
运算符``,它能够获得变量的地址 (这也意味着它的操作对象必须是变量),&不能对没有地址的东西取地址,比如a++,++a,a+b等等
简单来说,利用sizeof
,我们可以得到一个类型/变量内存占据位置的大小 使用&
,我们可以得到该变量在内存中的地址
顺带一提,用printf输出地址
时,使用%p , 而且取出的地址大小是否和int相同取决于编译器(32位架构还是64位架构)
1 2 3 4 int i = 0 ;printf ("%p" , & i) ; return 0 ;
指针是什么 我们一直所说的“指针”,是真正的能够存储地址 的变量
指针,指的就是保存地址的变量 我们使用星号*
表示某个变量,是一个指针
1 2 3 4 5 6 7 8 9 10 11 int *p,q ; 这里面的p是指针,而q就是普通的整型 #### 指针变量 普通变量的值是实际的值 指针变量的值是**具有实际值的变量**的**地址** \*是一个单目运算符,用来**访问**指针的值所表示的地址上的变量 它可以做右值,也可以做左值 ```c int k = *p ; *p = k+1 ;
由于指针变量的特殊性,我们若在函数中修改了指针变量,那么也会修改它所指向的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void f (int *p) ;void g (int k) ;int main (void ) { int i = 6 ; printf ("&i = %p\n" ,&i); f(&i); g(i); return 0 ; } void f (int *p) { printf (" p = %p\n" , p); printf (" p = %p\n" , *p); *p = 26 ; } void g (int k) { printf ("k = %d \n " , k); }
小结: 指针,地址,*和& 到这里也许有一些混乱,为了接下来内容的进行先进行一次小结: 1.什么是地址? 地址指的是”变量地址”,意思是在内存中,某个变量的值,被放置在了这里
2.怎么获得地址? 利用&
操作符可以获得地址,引用一句话:
每一个变量都有一个内存位置,每一个内存位置都定义了可使用 & 运算符访问的地址,它表示了在内存中的一个地址
3.怎么记住地址/怎么操作这块地址/什么是指针? 利用*
对某个变量进行操作,表示该变量存储着一个地址 ,这种变量就被叫为指针
其实意会一下就简单了,”指针”,意思就是这个变量的意义,就是”指向”某块地址
利用*
在一个变量之前,就是代表这个变量是个指针(用来存地址的) 那么怎么对这个指针操作呢,一般而言,可以使用&
符号来获得某个变量的地址,然后将其赋给指针(写) 而使用*
,则是得到所指定地址的变量的值(读)
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值.
4.实例 来自菜鸟教程的两个简单易懂的实例 C指针
第一个实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <stdio.h> int main () { int var = 20 ; int *ip; ip = &var; printf ("var 变量的地址: %p\n" , &var ); printf ("ip 变量存储的地址: %p\n" , ip ); printf ("*ip 变量的值: %d\n" , *ip ); return 0 ; }
上述代码编译会得到结果: var 变量的地址: 0x7ffeeef168d8 ip 变量存储的地址: 0x7ffeeef168d8 *ip 变量的值: 20
第二个实例:
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int main () { int var_runoob = 10 ; int *p; p = &var_runoob; printf ("var_runoob 变量的地址: %p\n" , p); return 0 ; }
得到结果: var_runoob 变量的地址: 0x7ffeeaae08d8 这里用图说明即是
指针与数组 函数的参数表中的数组,实质上就是个指针(这也是为什么写a[]和a[10]之类的是一样的), 因此在函数中我们不能直接用sizeof 得到正确的数组长度
函数参数表中的数组实际上是指针,但是可以用数组的运算符[]来运算
数组变量是特殊的指针,这使得它有如下性质 1.数组变量本身表达地址,所以我们取数组的地址时无需使用&
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int a[10 ] ; int *p = a ; ``` 2. 但是数组的单元表达的是变量,我们需要用&来取它。数组a的地址,等于数组单元a[0 ]的地址 > 可以想象为数组是一系列连续的指针地址构成的,其中第一位(下标为0 的)那一位代表整个数组的开始 3. *运算符可以对指针做,也可以对数组做 4. 数组变量是const 的指针,所以不能被**赋值** ## 字符类型 ### CHAR char 是最小的整数类型,同时也是一特殊的类型:字符 原因在于: 1. 用单引号表示的字符字面量 'a' , '1' 2.'' 也是字符 3. printf scanf 里用```%c```来输入输出字符 ```c char c ; char d ; c = 1 ; d = '1' ; printf ("c = %d \n" , c) ; printf ("d = %d \n" , d) ;
以上的两个1,一个是整型,而另外一个是字符(因此d打印出来是49)
%c 表示以字符的形式输入/输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 char c = 'A' ; printf ("%c \n" , c) ; c++ ; printf ("%c \n" , c) ; int i = 'Z' - 'A' ;printf ("%d \n" ,i ) ;s``` > a+'a' -'A' 可以把一个大写字母变成小写字母 > a+'A' -'a' 可以把一个小写字母变成大写字母 ### 逃逸字符 逃逸字符用来表示无法印出来的控制字符或特殊字符,它由一个反斜杆"\\" 开头 , 后面跟上对应的字符 \\b 回退一格 \\t 到下一表格位 (也就是制表位上的位置,是每行固定的位置(试着敲一下tab),利用\\t 可以使上下行对齐) \\n 换行 \\r 回车 \\" 双引号 \\' 单引号 \\\ 反斜杠本身 ## 字符串 在C语言中,字符串指以0(整数0)结尾的一串字符 0和'\\0'是一样的,但是和'0'是不一样的 0标志着字符串的结束,但是它不是字符串的一部分,计算字符串长度的时候也不包含这个0 字符串以数组的形式存在,也以数组或指针的形式访问(更多的是以指针的形式) 在string.h中有很多处理字符串的函数 ### 字符串变量 我们有多种方式表达字符串 ```c char *str = " Hello" ; char word[] = " Hello" ; char line[10] = " Hello" ; ``` 这里面 " Hello"被称为**字符串常量**," Hello"会被编译器变成一个字符数组放在某处,这个数组长度是6,结尾还有表示结束的0(Hello五位,0一位,共六位) 两个相邻的字符常量会自动连接 ### 小结 C语言的字符串是以字符数组的形态存在的,不能用运算符对字符串做运算,通过数组的方式可以遍历字符串 唯一特殊的地方是字符串字面量可以用来初始化字符数组 以及标准库提供了一系列字符串函数 ### 字符串常量(续) ```c char* s = " Hello , world!"; //我要指向某个地方的字符串 ``` s是一个指针,初始化为指向一个字符串常量 由于这个常量所在的地方,实质上是s是const char* s , 不过由于历史原因,编译器接受不带const的写法 但是当我们试图对s所指的字符串做写入的时候会导致严重后果 当我们编译过程中有两个相同的字符串(比如s1 s2 两个字符串都是Hello world),它们会指向同一个地方 如果想要制作一个能修改的字符串,那么在**一开始**就需要用**数组**定义 ```c char s[] = " Hello, world!" ; //某个地方的字符串就在这里 ``` ##### 区别 ```c int i =0 ; char *s = " Hello , World"; char *s2 = " Hello,World" ; char s3[] = " Hello,World"; printf(" &i=%p\n", &i) ; printf(" &s =%p\n", &s) ; printf(" &s2=%p\n", &s2) ; printf(" &s3=%p\n", &s3) ; s3[0] = 'B' ; printf(" Here!s3[0 ] = %c\n",s3[0]); return 0 ; ``` 该部分输出会类似: &i=0x7ffe7f63052c &s =0x7ffe7f630520 &s2=0x7ffe7f630518 &s3=0x7ffe7f63050c Here!s3[0] = B 数组字符串:这个字符串在这,作为本地变量会被自动回收 指针字符串:不知道这个字符串在哪,需要处理参数,可以动态分配空间 如果要构造一个字符串-->数组 如果要处理一个字符串-->指针 >字符串可以表达为char\*的形式,char\*不一定是字符串,只有在它所指的字符数组有结尾0,我们才能说它所指的是字符串 ## 字符串计算 ### 赋值 ```c char *t = " title" ; char *s ; s = t ; ``` 实际上并没有产生新的字符串,只是让指针s指向了t所指的字符串。对s的任何操作就是对t做的,**因为二者指向同一块地址** ### 输入输出 %s代表输入输出的是字符串 ```c char string[8]; scanf(" %s",string); printf(" %s",string); ``` >scanf读入一个单词,到空格、tab、回车为止 想要在空格tab回车之后继续读,我们需要再来一个scanf,而且第二个scanf是不会读到" 空格tab回车"的 但是scanf实质上是不安全的,因为不知道要读入的内容的长度 在百分号和s中间,可以增加一个数字,表示我们希望最多可以读入多少字符,以此提高安全性。此时就不一定是以空格tab回车来区分了,读完了,这个scanf就结束了 ##### 常见错误 ```c char *string ; scanf(" %s",string);
因为char*是字符串类型,定义了一个字符串变量string就可以直接使用了,但实际上这种做法是十分危险的,因为你不知道使用者会读入多少内容
char buffer[100] = ""; //空字符串,buffer[0] == '\0'
char buffer[] = "" ;//数这个数组的长度只有1!
字符串_附录 字符串相关的更多补充请见《五、字符串补充》