《C语言与指针》学习备忘

这里记录的是我在学习《C语言与指针》这本书的过程中,按照从前到后的顺序记录下来的重要内容。

  1. 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.
    fgetc()不仅是遇到文件结尾时返回EOF,而且当发生错误时,也会返回EOF。所以C语言又提供了feof()函数,用来保证确实是到了文件结尾。如果一个文件含有n个字符,那么while循环的内部操作会运行n+1次。所以,最保险的写法是像下面这样:
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;
  fgets()读取行分为两种情况:(buf的长度大于等于cont)
  (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',而非自己想要的值。

扫描二维码关注公众号,回复: 1028423 查看本文章

这里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++; 






猜你喜欢

转载自blog.csdn.net/jinking01/article/details/79824141