C语言程序设计笔记
本系列笔记是学习复盘慕课上浙江大学 翁恺老师《程序设计入门-C语言》课程的笔记和一些自己的总结。(文章的部分截图来自课程视频截图)
课程链接:https://www.icourse163.org/learn/ZJU-199001?tid=1206771253#/learn/content
第八周:指针与字符串
-
-
指针
- 取地址运算
-
指针
- Sizeof是一个运算符,给出某个类型或变量在内存中占据几个字节。 %ld
Int:4个字节。(32 bit)
Double:8个字节(64bits)
- 运算符& :
作用:取得变量的地址;因此它的操作数必须是变量。 %x 16进制输出
输出地址应该用%p
原因是为什么呢?因为int和&i的位数是不一样的。
一个是4,一个是8(在电脑64位的架构下)
地址的大小取决于编译器,取决于你的电脑,取决于你是64位架构还是32位架构
- &不能对没有地址的东西取地址 必须有明确的变量
不可以: &(i++)&(a+b) &(a++) &(++a)
数组
- 结论:在数组中,&a=a=&a[0]
结论
-
-
- 指针
-
取地址有什么作用呢?
- 如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量? Scanf(“%d”,&i);
- 我们使用一个变量来存储地址,这个变量就叫做指针。
Int i;
Int* p=&I;
Int* p,q; (Int *p,q)表示:P是指针类型的变量,p是普通的int变量。
*和p是在一起的;*p是一个int,所以p是一个指针。没有int*这种数据类型
我们说p指向i:说的意思是p里面存储的是变量i的地址。
指针变量:
变量的值是内存的地址。
普通变量的值是实际的值。指针变量的值是具有实际值的变量的地址。
- 指针在函数中的应用。
一个函数的原型: void f(int *p)在被调用的时候得到了某个变量的地址。
我们使用函数时: f(&i) 我们在函数里面可以通过这个指针访问外面的这个i。访问意味着读或者写。我们之前是一直只能传进去值,而不能够访问变量改变它的值
- *是一个单目运算符,用来访问指针的值多表示的地址上的变量。如何改变变量的值?
Int k=*p; *p=k+1;
(*p指向的变量的值 p地址的值)
- *是传入地址时我们使用scanf(“%d”,i)为什么没有报错? 因为编译器以为我们传入的是i的地址。然后那这个地址来做事情。把写入的数字写到不该写的地方。运行会出错。
-
-
- 指针与数组
-
- 传入函数的数组成了什么?&a=a=&a[0]看a的地址,这三个都可以
通过在主函数和定义函数中的sizeof是不同的,我们得到:
- 在minmax里看到数组的地址和主函数的数组的地址一样,说明传入函数的数组和原先的数组就是同一个东西。
- 说明在函数中修改数组中单元的值,会改变原先数组的值。
我们看到,sizeof(a)是不一样的,但是地址是一样的,所以说明我们函数参数表上传入的a[]是一个指针。这就是为什么我们不能出传入a【13】写了个数也没用;为什么我们前面说过在自己定义的函数中不能求得正确的数组长度了,原因在于传入的是指针。a[]是一个指针。
不是说数组和指针等价,是在函数参数表中出现的时候,作为函数原型,他们等价。
- 数组变量是特殊的指针。数组变量本身表达地址,所以无需用&取地址。
Int a【10】
Int *p=a;即可
- 但是单个的数组单元表达的是变量,需要用&取地址。
急急急 a==&a【0】
- []运算符可以对数组做,也可以对指针做。
P[0]==a[0]
对于数组,我们有:
&a=a=&a[0];
我们将p当成数组,则
&p=p=&p[0]
则有: *p=p[0]
Jijiji
- *运算符可以对指针做,也可以对数组做。
*a=25
* 数组变量是const的指针,是常量指针,所以不能被赋值。所以对一个数组赋值另一个数组,不可以直接 int b【】=a这样来赋值。
* 如果对const指针的所有的操作都可以对数组变量做,而且结果一致,就说明数组变量就是指针;如果有某个操作不能做,或者结果不一致,就说明不是指针
关于指针的三个小题目:
1.
这里我要说:
*p=&a[5] 是表示p是一个指针变量,指向的是a[5]的地址;如果把p当成数组,则数组的地址可以表示为: *p=p=&p[0]
因此p[0]=a[5]
所以p[-2]=a[3] 所以a[3]=54
把p当成数组,则有:
*p=p=&p[0]=*a=a=&a[0]
因为地址一样,所以p【0】和a【0】指向的是同一个地址的值,则相同。
因为p表示的是a[1]的地址,因此&p[0]=&a[1];因此p[2]=a[3]=54
-
-
字符类型
- 字符类型
-
字符类型
- Char是一种整数,也是一种特殊的类型:字符。这是因为:
- 用单引号表示的字符字面量:‘a’‘1’
- ‘’是一个字符
- Printf和scanf里用%c来输入字符。
- 字符的输入和输出
‘1’和1赋给字符变量是一样的吗?如何输入字符‘1’这个字符给char c
D表明:在计算机内部,‘1’是49.我们在计算机内部是用49这个值来表达字符‘1’的。
每一个字符,在计算机内部都有一个值来表达它。
输出:
C输入为整数,输出的字符用%d输出是和c一样的,%c是此整数表示的字符
C输入为整数,输出的%d就是此整数,输出的字符是此整数表示的字符。
- ‘1’的ASCII编码是49,所以当我们写判断条件,比如说c==‘1’其实在计算机内部是去判断49?=49的,所以说如果我们写c==49时,就表示c代表的字符是‘1’。
当字符作为判断条件时,我们判断的是什么?是字符在计算机内部的数值。
有空格和无空格有什么区别?
代码有空格/输入有空格:1.输入两个整数,我们得到的正确的结果。
代码有空格/输入无空格:输入一个整数,一个字符。我们也得到了正确的结果。
代码有一个空格、输入有两个空格,两个整数。我们还是可以得到正确的结果。
代码无空格:
我们读到的是空格的ascii码32.
Why?代码中有没有空格是不一样的;有空格表示%d不仅要把第一个数字读完,还要把后面的空格都读完。如果没有空格,表示只读到第一个数字,最后一个例子中,只读到12,而12后面的空格不读,而第二个读字符的时候读到的就是空格了。
我们读
- 因为字符是一种整数,因此它可以做整数的运算。
- 结论:
- 一个字符加一个数字得到ASCII吗表中那个数之后的符
- 两个字符的减,得到它们在表中的距离。
- 字母在scaii表中是谁许排列的。大写字母和小写字母是分开排列的,并不在一起。
-
-
- 逃逸字符
-
- 用来表达无法印出来的控制字符或特殊字符,它由一个反斜杠“\”开头,后面跟上另一个字符,这两个字符合起来,组成了一个字符。\+”来使得双引号得到显示。
回车\r
\t: 制表位。每一行都有一些固定的位置。
- 一个\t使得输出从下一个制表位开始
- 用\t才能使得上下两行对齐。 (敲table可以看得到)
4会显示在相同的位置上。
\n在系统中会被翻译成做 \r\n 回车和换行两个动作。
字符串
- 字符串
- 如何定义C语言的字符串?
第二种方式用来定义字符串。最后一定要有一个‘\0’,就是表示0,也可以直接写0.
最后的0使得word是一个字符串。
- 对于C语言而言,字符串是以0结尾的一串字符;0和‘\0’是一样的,但是和‘0’不一样!!
- 0标志字符串的结束,但它不是字符串的一部分
- 计算字符串长度的时候不包括这个0
- 字符串以数组的形式存在,以数组或指针的形式访问。更多的是以指针形式访问。
- String.h中有很多处理字符串的函数。
字符串变量:
Char *str=”Hello”; 有一个指针,指向的地方里面放的是hello。地址是哪?
Char word[]=”Hello”;字符数组,里面的内容是hallo
Char line[10]=”Hello” 数组大小是10;里面有hello,占据6个字符的空间。
字符串常量:“Hello”会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0.
相邻的字符串常量会被连接起来。
如果采用第二种方式\来继续一个字符串,那么第二行前面的哪些空格部分也会被显示出来。
字符串:
- C语言的字符串是以字符数组的心态存在的
- 不能用运算符对字符串做运算
- 通过数组的方式可以遍历字符串。
- 个人认为,C语言更关注于处理数字的能力,比如说对于字符串而言,它不能还是用运算符对字符串做计算,但是标准库还是提供了一系列的字符串函数。而后面的年代出现的编程语言更关注于对于文字的处理能力。
因为计算机内部的存储和功能都是基于数字化的,因此系统的构建还是需要C语言的。
- 唯一特殊的地方是字符串字面量可以用来初始化字符数组。
-
-
- 字符串变量
-
两种形式。
1.char* 表示s是一个字符串
Char* s=“Hello World!”;
2,。用字符数组
从这个例子中我们可以看出:
本地变量放在内存中较大的地方;字符串变量放在较小的地方。
- S和s2指向的是相同的地方。指向的那个地方它们位于程序的代码段;而且是只读的。
Jjjj
所以在刚刚的代码里,我们无法进行操作s【0】=“B”这种写的操作。因为代码段在操作系统中是被保护的,是不可以写的,只能读的。而一开始那个叫初始化。
- 字符串常量char* s,这个s是一个指针,初始化为指向一个字符串常量。由于这个常量所在的地方是代码段,所以实际上s 是 const char *s;但是由于历史的原因,编译器接受不带const的写法。但是试图对s所指的字符串做写入会导致严重的后果。
- 如果需要修改字符串,应该用数组的方式来定义一个字符串;char s【】=“Hello Wrold”
Jjjj
数组定义的字符串常量,在内存中什么位置?
不在程序端,和本地变量存放的是同样的位置;并且能做修改。(程序例子中的s3);
- 数组还是指针?
数组:这个字符串在这里。作为本地变量,空间会被自动回收。
指针:这个字符串不知道在哪里;我们通常用它来做:
- 处理参数
- 动态分配空间。Malloc
- 这个字符串就只读。
如果你要构造一个字符串,我们用数组。如果你要处理一个字符串,用指针。
- Char* 不一定是字符串。本意是指向字符的指针,可能指向的是字符的数组。
- 只有它所指的字符数组有结尾的0,才能说它所指的是字符串。
8.4字符串计算
8.4.1字符串输入输出。
- 字符串赋值?
Char *t=”title;
Char *s;
S=t
并没有产生新的字符串,知识让指针s指向了t所指的字符串,对s的任何操作就是对t做的。
所谓的字符串赋值,并没有制造一个新的字符串出来。
- Scanf和printf对于string类型也有特殊的手段来做输入输出。
- 读到什么位置?
读到空格 tab 或者回车为止。
- Scanf是是不安全的,因为不知道要读入的长度;想上面那副图,string数组的长度是8,但是string读入的内容长度是多少不知道。
Abort trap:数组越界
Jjj
- 安全的做法是在scanf的%s的%和s之间加入数字,表明,你最多只能读多少个字符。这个数字应该比数组的大小小1,(因为最后需要一个0)
超过7个字符,就不要了。如果你程序要读两个,第一个输入例如12345678超过了7个,则把1234567给了第一个,然后剩下的8给第二个,而不会再要你输入。
Jjj
常见错误!!!
常常写第一和第二行的程序,这是错的。第一行只是定义了一个指针变量,指向内存中的某块空间,没有被初始化。本地变量没有初始值,原来内存空间是什么就是什么。
- 空字符串,第一个buffer【0】==‘\0’;空字符串的数组长度是1.
8.4.2字符串函数
字符串赋值?
String.h里常用来处理字符串的函数
- Strlen(作为参数时,指针和数组是一样的),如果我门把数组传入函数时,希望函数不来修改我们的字符串,我们应该写成const类型。
Sizeof是包括那个结尾的0的。
- Strcmp
比较字符串的大小不可以用s1==s2。数组的比较永远是false;数组变量永远是不同的地址,当我们比较两个数组比较的是两个数组的地址是否相等,因此永远会是false。
得到的是负数,就是s1<s2.
- Strcpy
Src放在右边(像赋值一样)
而且dst和src指向的是不同的存储空间
- Strcat
做连接。把s2拷贝到s1的后面,像做加法一样,s1+s2,返回的指针是s1
Strcpy和strcat都有可能出现安全问题,如果目的地没有足够的空间怎么办?
那怎么办呢?用安全版本。中间多了一个n
N的含义是:你能够考过去最多多少个字符,多了就会掐掉。
Strncmp的作用:n不是为了安全,是有时候我们只在意前几个字符是不是相等,而不在于整个字符串是否相等。N表示比较前n个字符。
- Strchr
在字符串中找字符。第一次出现的位置,返回的是指针。第二个多加了一个r,表示从右边开始找。
- Strstr