这里记录的是我在学习《C语言与指针》这本书的过程中,按照从前到后的顺序记录下来的重要内容。
- EOF、NULL、0、\0、\n的区别
- EOF是end of file的缩写,是流或者文件的结束符。它是stdlib中定义的一个常量,为-1。#define EOF (-1)。Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出"标准输入"的缓存区,所以这时必须按两次Ctrl-D);Windows中,Ctrl-Z表示EOF。(顺便提一句,Linux中按下Ctrl-Z,表示将该进程中断,在后台挂起,用fg命令可以重新切回到前台;按下Ctrl-C表示终止该进程。)
- NULL是空指针的意思,在C语言里指的是(void*)0, 而在C++中是0;
- \0是一个字符,是字符串的结束符,其对应的ascii码就是0.
- \n是换行符,对应的ascii码是10.
int c = fgetc(fp); while (c != EOF) { do something; c = fgetc(fp); } if (feof(fp)) { printf("\n End of file reached."); } else { printf("\n Something went wrong."); }2. getchar(),scanf(),gets()和fgets()
- getchar()读取输入的字符(%c),它能读取任何字符(只要是在ACSII上的)如回车(ACSII码值:10)、换行(13)、null(\0)都可以,所以使用的时候要做的是判断.getchar()函数等待输入直到按回车才结束,回车前的所有输入字符都会逐个显示在屏幕上,但只有第一个字符作为函数的返回值。getchar函数原型如下:
函数格式:int getchar(void);
功 能:从标准输入设备读取下一个字符
返 回 值:返回所读字符,否则返回-1(也可以表示为EOF) - scanf("%s",s);遇到空格,换行符,tab 会终止并跳过,但这些符号仍会存留在输入队列中,如:
char bb[10]:
if(scanf("%s",bb)==1)
{
puts(bb);
char ch=getchar();
putchar(ch);
}
输入:hello a
输出为:
hello
_(一个空格)
char bb[10]; if(scanf("%s",bb)==1) { puts(bb); //char ch=getchar(); //putchar(ch); scanf("%s",bb);//跳过空格,继续读取下一个字符串 puts(bb); } 则输出: hello a
原因:当scanf()读取“hello”后遇到空格,停止读取,把“hello”放入bb中,后面的空格仍然停留在输入 队列中,所以后面的getchar()首先读到的是这个空格而不是后面的字符"a".
由于scanf基于指针实现,所以scanf()读入基本数据类型时,需要在变量名前加&;读入字符串数组时,则不需要加&。(1) scanf("%d%d",&a,&b),一次读入两个整数,在输入两个数据时,遇到第一个空白符号结束读入,开始第二个数据读入,这两个整数的获取没有问题。(2) 但scanf()函数在读入单个字符时scanf("%c",&a)时,因为空格、tab等都是字符,所以要小心缓冲区中空格,换行符等被读入,这种情况可以在百分号前加入空格等过滤,具体与scanf()的实现有关。(3)scanf("%s",name)读入字符串,如果字符串中有空白符,则读到空白符结束。scanf()函数使用空白、制表符、换行符、空格把输入分成多个字段,从而完成对多个字段的读入。在依次把转换说明和字段匹配时跳过空白等。在使用scanf()函数读入时,应该避免用于读入带空白等字符的字段。另外应该对scanf()函数发生不匹配时,无限占用缓冲缓冲区进行处理,可用两种方法,对scanf()返回值进行检查,若不匹配则直接退出。或者刷新缓冲区。
- gets()接收直至换行符\n或EOF。换行符被转换为null值(\0),并由此来结束字符串,gets(char *str)会把所有字符读入str,换行符'\n'会读取但不会保存,最终将被舍弃,gets函数可能会造成str溢出。
- fgets(char *buf,int cont,FILE *fp),从fp所指流中读取最多cont-1个字符存入buf中,避免了溢出问题,当遇到文件结束符EOF或读取错误时返回NULL;
(1)行的长度大于cont,则会读取cont-1个字符存入buf中,且第cont个字符自动赋为‘\0’
(2)行的长度小于cont,即buf未满就遇到换行符,则把所有字符存入buf中,且保留换行符'\n',并且在最后附加'\0'
困惑解答:语句:ch=getchar();的作用等同于:scanf("%c",&ch); 在输入时,空格,回车等都将作为字符读入,而且只能在用户敲入Enter键时,读入才开始执行.因此,在用scanf() 输入时,最好加一个 \n, 这样下接 getchar(); 时不会犯错:
scanf("%d...\n",&i,...); ch = getchar();
下面给出一个例子,从标准输入接收一些字符,然后计算它们的和,可能是负值,初始值为-1:
#include <iostream> #include <string> #include <stdio.h> using namespace std; int main(){ signed char checksum=-1; char ch; while((ch=getchar())!=EOF && ch!='\n') checksum = checksum+ch; ch = getchar();//把最后面的回车算进去,一行的内容是包含回车的 checksum=checksum+ch; cout<<(signed int)checksum<<endl; return 0; }
while((ch=getchar()) !=EOF && ch != '\n')的作用:主要的目的为下次读取数据做准备。
如:输入csdn并回车,输入流中保存的内容为csdn\n,而scanf读取输入是只取了csdn,‘\n’还在输入流里,如果直接读取下一个值就会读到'\n',而非自己想要的值。
这里EOF=-1一般是文件的结束符(不是合法的字符),但是,在windows里面,你可以用 ctr+z来输入一个EOF结束符,而'\n' = 10是一个合法字符。
3. 为什么不能直接向char* 的变量写内容?
char *string = NULL; scanf("%s",string); 或者 char * string; cin>>string;
因为你试图访问非法的内存地址:char *string = NULL;时,这个地址是0,直接会表现出来。char *string, 也是会出问题的,只是没有表现出来,如果没有初始化string,那么指针的值是一个随机值。只要不是预先分配好的空间,你往里写入东西就是错误的。
char *string = "hello";//这个其实等价于const char* string="hello",这是一个指向字符串常量的指针,字符串不能修改,但是指针的值可以修改,也就是可以指向其他的地方 scanf("%s",string); 还是会出问题。这是为什么?
这是因为,这个其实等价于const char* string="hello",这是一个指向字符串常量的指针,字符串常量保存在常量区域,不能进行修改,但是指针的值可以修改,也就是说这个指针可以指向其他的内容。不允许string[0]='a'之类的操作,但是允许char* p ; ... ; string =p。
int main(){
char * str="abcd";
str ="1234";
cout<<str<<endl;
return 0;
}
这个是可以的,string是指向字符串常量的指针,但是会有warning:warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
char * str="abcd";和warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings] str ="1234";
可以看到,警告说明,你试图将一个字符串常量转成char*字符串变量,这个是没有问题的,因为常量可以当做变量,但是变量不能当做常量。
int main(){ signed char checksum=-1; char * str="abcd"; cin>>str; cout<<str<<endl; return 0; }这个就报错, 无法通过编译,原因就是上面所说的,这里的str只能指向字符串常量,这里指向的是"abcd"这个位置,而cin>>str在没有修改str的指向地址的情况下,进行赋值,相当于往“abcd”所在的地址写新的内容,显然是无法写入的。
int main(){
signed char checksum=-1;
char * str="abcd";
char p[100] ;
cin>>p;
str=p;
cout<<str<<endl;
return 0;
}
int main(){
signed char checksum=-1;
char * str="abcd";
char p[100] ;
str=p;
cin>>p;
cout<<str<<endl;
return 0;
}
上面这两个例子都是可以正常运行的。
已知char * str="abcd"; str="1"可以,str的值变了,指向新的地址了。 *str='1';试图修改字符str[0]的值(因为str是指针,值是字符串"abcd”的首地址,*str和*str[0]是一样的),错误; str='1';错误,试图把指针赋值为一个char型(一个字节),而指针是char*型,占4个字节; *str="1";error: invalid conversion from ‘const char*’ to ‘char’ [-fpermissive] *str="1"; str的值不变(还是“adbd”的首地址),这里将str指向的区域的内容进行修改,也是错误的。
总结:针对一个字符指针,能否直接向它写入数据,或者直接接受scanf或者cin的输入,主要是看这个字符指针的指向,是否初始化了地址、是指向字符串常量,还是指向NULL或者指向数组等,主要是看它指向的地方是否允许修改!!!不管指针怎么转来转去,最终要找到指针所指向的区域是否已申请空间,是否位于常量区等。
4. 输入函数:cin、cin.getline()、getline()的用法
(1)cin>>
用法1:输入一个数字或字符
cin>>a>>b;
用法2:接收一个字符串,遇“空格”、“TAB”、“回车”就结束
char a[20]; cin>>a; cout<<a<<endl; 输入:jkljkljkl 输出:jkljkljkl 输入:jkljkl jkljkl //遇空格结束 输出:jkljkl(2) cin.getline()
用法:接收一个字符串,可以接收空格并输出
char m[20];
cin.getline(m,5);
cout<<m<<endl;
输入:jkljkljkl
输出:jklj
接收5个字符到m中,其中最后一个为'\0',所以只看到4个字符输出;
如果把5改成20:
输入:jkljkljkl
输出:jkljkljkl
输入:jklf fjlsjf fjsdklf
输出:jklf fjlsjf fjsdklf
延伸:
1、cin.getline()实际上有三个参数,cin.getline(接收字符串的变量,接收字符个数,结束字符)
2、当第三个参数省略时,系统默认为'\0'
3、如果将例子中cin.getline()改为cin.getline(m,5,'a');当输入jlkjkljkl时输出jklj,输入jkaljkljkl时,输出jk
int a; cin>>a; cout<<a<<endl; char b[10]; cin.getline(b,10); cout<<b<<endl; cin.getline(b,10); cout<<b; 输入: 5 hello 输出: 5 空行 hello
int a; cin>>a; cout<<a<<endl; char b[10]; cin<<b; cout<<b; 输入: 5 hello 输出: 5 hello
因为cin自动把enter忽略了,而cin.getline不行,因为默认是遇到'\n'就结束读入。
(3)getline()
用法:接收一个字符串,可以接收空格并输出,需包含“#include<string>”
string str; getline(cin,str); cout<<str<<endl; 输入:jkljkljkl 输出:jkljkljkl 输入:jkl jfksldfj jklsjfl 输出:jkl jfksldfj jklsjfl
5. const关键字
在函数声明的时候,如果形参在函数内部不会被更改,或者要求函数不能更改形参,则建议用const来限定这个形参。
6. 换行符\n不是字符串的结束符\0
一般我们在命令行输入的时候,会敲一个回车,表示输入完啦。但是\n并不是字符串的结束符。所以在使用标准输入流的时候要注意这个回车到底怎么处理,在你的字符串中是否需要保留这个\n。不同的函数,对\n的处理方式不同,这个可以参考本文前面几个知识点。读取文件的时候,文件中也是可能存在换行符的,如一个段落,这个换行符在处理的时候也是需要考虑的。
7.回车符和换行符的区别
换行是光标从当前位置换到下一行,这个相当于光标在段中的时候按下方向键的↓,光标会移动下一行的相同位置。
回车是光标从当前位置回到本行的行首,这个相当于在一行中按下home键,光标回到行首。
换行回车效果就是前面两个效果的叠加,也就是换到下一行,并回到行首。
8. 指针操作
char ch='a';
char * cp=&ch;
printf("ch的地址%p\n",&ch);
printf("cp的地址%p,cp的值%p,cp指的值%c\n",&cp,cp,*cp);
char* p=cp;
cout<<"p的地址"<<&p<<endl;
*p=*cp++;
printf("cp的地址%p,cp的值%p,cp指的值%c\n",&cp,cp,*cp);
printf("p的地址%p,p的%p,p指的值%c\n",&p,p,*p);
从输出结果可以看到,*p=*cp++之后,cp的地址不变,cp的值变化,自然cp指向的值也发生了变化。而p呢,则和原来的cp一样,这说明这个语句相当于:p=cp; cp=cp++;从这里可以看到如果定义一个指针cp,指向一个数组,那么*cp++可以用于循环,for(cp=&a[0];cp<&a[n-1];) *cp++;