8.指针、字符串
基础C语言的最后一章了,也是(对新手)比较晦涩的一章
如果您学C只是为了玩玩或者练算法,那么差不多已经够用了
如果您有更大的目标,编程嘛,总是无止境的
指针
导入
我们从sizeof谈起:它是一个运算符。给出某个类型或变量在内存中占据的位置,以字节大小表示(1字节=4比特)
接着是&,它实质上是一个运算符,它能够获得变量的地址(这也意味着它的操作数必须是变量)
顺带一提,用printf输出地址时,使用%p , 而且取出的地址大小是否和int相同取决于编译器(32位架构还是64位架构)
int i = 0 ;
printf("%p" , & i) ;
return 0 ;
&不能对没有地址的东西取地址,比如a++,++a,a+b,…
指针
我们一直所说的“指针”,才是真正的,能够完美存储地址的变量
指针就是保存地址的变量
我们使用星号* 表示我们所想要的这个变量,是一个指针
星号是“加给”变量的,比如
int *p,q ; 这里面的p是指针,而q就是普通的整型
指针变量
变量的值是内存的地址
普通变量的值是实际的值
指针变量的值是具有实际值的变量的地址
*是一个单目运算符,用来访问指针的值所表示的地址上的变量
它可以做右值,也可以做左值
int k = *p ;
*p = k+1 ;
由于指针变量的特殊性,我们若在函数中修改了指针变量,那么也会修改它所指向的值
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 ; //就在这里,把p变量的地址指向的那个变量(就是i)改为26
}
void g (int k){
printf("k = %d \n " , k);
}
指针与数组
函数参数表中的数组,实质上就是个指针(这也是为什么写a[]和a[10]之类的是一样的),也因此在函数中我们不能直接用sizeof得到正确的数组长度
函数参数表中的数组实际上是指针,但是可以用数组的运算符[]来运算
实际上,数组变量是特殊的指针
1.数组变量本身表达地址,所以我们取数组的地址时无需使用&
int a[10] ;
int *p = a ;
2.但是数组的单元表达的是变量,我们需要用&来取它。数组a的地址,等于数组单元a[0]的地址
3.*运算符可以对指针做,也可以对数组做
4.数组变量是const的指针,所以不能被赋值
字符类型
CHAR
char是最小的整数类型,同时也是一特殊的类型:字符
原因在于:
1.用单引号表示的字符字面量 ‘a’, ‘1’
2.’'也是字符
3.printf scanf 里用%c来输入输出字符
char c ;
char d ;
c = 1 ;
d = '1' ;
printf("c = %d \n" , c) ; // 结果是1
printf("d = %d \n" , d) ; // 结果是49
以上的两个1,一个是整型,而另外一个是字符(因此d打印出来是49)
%c 表示以字符的形式输入/输出
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’ 可以把一个小写字母变成大写字母
逃逸字符
逃逸字符用来表示无法印出来的控制字符或特殊字符,它由一个反斜杆"\" 开头 , 后面跟上另外一个字符
(如果打开md看,会看见上面那个反斜杠实际上是两个)
\b 回退一格
\t 到下一表格位 (也就是制表位上的位置,是每行固定的位置(试着敲一下tab),利用\t 可以使上下行对齐)
\n 换行
\r 回车
\" 双引号
\’ 单引号
\ 反斜杠本身
字符串
在C语言中,字符串指以0(整数0)结尾的一串字符
0和’\0’是一样的,但是和’0’是不一样的
0标志着字符串的结束,但是它不是字符串的一部分,计算字符串长度的时候也不包含这个0
字符串以数组的形式存在,也已数组或指针的形式访问(更多的是以指针的形式)
在string.h中有很多处理字符串的函数
字符串变量
我们有多种方式表达字符串
char *str = "Hello" ;
char word[] = "Hello" ;
char line[10] = "Hello" ;
这里面 "Hello"被称为字符串常量,"hello"会被编译器变成一个字符数组放在某处,这个数组长度是6,结尾还有表示结束的0
两个相邻的字符常量会自动连接
小结
C语言的字符串是以字符数组的形态存在的,不能用运算符对字符串做运算,通过数组的方式可以遍历字符串
唯一特殊的地方是字符串字面量可以用来初始化字符数组
以及标准库提供了一系列字符串函数
字符串常量(续)
char* s = "Hello , world!"; //我要指向某个地方的字符串
s是一个指针,初始化为指向一个字符串常量
由于这个常量所在的地方,实质上是s是const char* s , 不过由于历史原因,编译器不接受带const的写法
但是当我们试图对s所指的字符串做写入的时候会导致严重后果
当我们编译过程中有两个相同的东西(比如s1 s2 两个字符串都是Hello world),它们会指向同一个地方
如果想要制作一个能修改的字符串,那么在一开始就需要用数组定义
char s[] = "Hello, world!" ; //某个地方的字符串就在这里
区别
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 ;
数组字符串:这个字符串在这,作为本地变量会被自动回收
指针字符串:不知道这个字符串在哪,需要处理参数,可以动态分配空间
如果要构造一个字符串–>数组
如果要处理一个字符串–>指针
字符串可以表达为char*的形式,char*不一定是字符串,只有在它所指的字符数组有结尾0,我们才能说它所指的是字符串
字符串计算
赋值
char *t = "title" ;
char *s ;
s = t ;
实际上并没有产生新的字符串,只是让指证s指向了t所指的字符串。对s的任何操作就是对t做的
输入输出
%s代表输入输出的是字符串
char string[8];
scanf("%s",string);
printf("%s",string);
scanf读入一个单词,到空格、tab、回车为止
想要在空格tab回车之后继续读,我们需要再来一个scanf,而且第二个scanf是不会读到"空格tab回车"的
但是scanf实质上是不安全的,因为不知道要读入的内容的长度
在百分号和s中间,可以增加一个数字,表示我们希望最多可以读入多少字符,以此提高安全性。此时就不一定是以空格tab回车来区分了,读完了,这个scanf就结束了
常见错误
char *string ;
scanf("%s",string);
以为char*是字符串类型,定义了一个字符串变量string就可以直接使用了,但实际上这种做法是十分危险的
char buffer[100] = ""; //空字符串,buffer[0] == '\0'
char buffer[] = "" ;//数这个数组的长度只有1!
string.h
strline : 传入一个字符串,返回它的长度,另外,返回的结果不包括那个0
strcmp:比较两个字符串,int strcmp(const char *s1 ,. const char *s2),s1==s2,返回0,s1>s2返回1,s1<s2返回-1 、
当二者不相等时,返回的是差值。比如比较"abc"和"Abc",返回32
!数组的比较,永远是false。想要用if语句判断,我们需要补充比较结果(0,1,-1)
strcpy: char* strcpy(char* restrict dst,const char restrict src)
把src1的字符串拷贝到dst,在这里restric表示src和dst不重叠(C99)
strcat: char strcat(char*restrict s1 , const char *restrict s2)
把s2拷贝到s1后面,形成一个长的字符串。返回s1,意味着s1需要有足够的空间
strcpy和strcat,都可能出现安全问题,我们还有安全一点的版本使用。那就是strncpy,strncat
它们在结尾还需要额外传入一个参数n,代表最多可以拷贝多少个字符
另外还有类似的strncmp,最后传入的n可以控制比较前多少个字符