C 知识积累 替换gets函数 Linux C 语法分析 switch和if else的比较

您所在的位置:网站首页 gets函数源码 C 知识积累 替换gets函数 Linux C 语法分析 switch和if else的比较

C 知识积累 替换gets函数 Linux C 语法分析 switch和if else的比较

2023-12-17 14:16| 来源: 网络整理| 查看: 265

目录 替换gets函数gets()用处gets()的危险之处gets()的几种替代方法一、用%c循环输入直到遇到换行结束二、用getchar()循环输入直到遇到换行结束三、scanf的另一种用法四、c++中的getline()方法五、解决方案使用fgets代替 回车与换行一.知其然二.知其所以然 关键字,操作符和函数区别1:关键字2:操作符3:函数 命令行参数argv原码补码补码加法 Linux C 语法分析结构体指针类型函数宏定义其他 const语法整理switch和if else的比较#pragma once1、#pragma once有什么作用?2、两者的使用方式有何区别3、两者各有何特点(1)#ifndef(2)#pragma once 4、两者之间有什么联系? 逗号表达式C 嵌入式代码整理获取键盘输入为结构体内元素单独赋值(Linux中)字符串拷贝输出程序运行系统功能C结构体定义

替换gets函数 gets()用处

gets从标准输入设备读字符串函数,其可以无限读取,不会判断上限,可以包含空格,以回车结束读取。

gets()的危险之处

因为该函数可以无限读取,所以应该确保buffer的空间足够大,以便在执行读操作时不发生溢出。如果溢出,多出来的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,破坏一个或多个不相关变量的值。这个事实导致gets函数只适用于玩具程序。

gets()的几种替代方法

既然gets()的用处是用来读取一个包含空格的字符串,那么我们就有了以下几种方法来代替gets():

一、用%c循环输入直到遇到换行结束 #include #include #include #include using namespace std; int main() { char str[100]; char ch; int i=0; while(scanf("%c", &ch) && ch != '\n') { str[i++] = ch; } cout str[i++] = ch; } str[i] = '\0'; //手动吧字符串末尾字符赋值成'\0' cout str[i++] = ch; } str[i] = '\0'; //手动吧字符串末尾字符赋值成'\0' cout int n; while(skip() && scanf("%d", &n)!=EOF) printf("%d\n", n); }

输出为:

1235 86 四、c++中的getline()方法

getline不是C库函数,而是gcc的扩展定义或者C++库函数。它会生成一个包含一串从输入流读入的字符的字符串。

具体用法:

getline(cin, str);

需要注意的是,str字符串必须是C++中的string字符串类型

也就是说必须包含头文件

#include

并且str必须定义为string类型

string str;

需要注意的是,既然str定义的是string类型,则说明求字符串长度函数strlen()将不再可用,C++提供了另一种方法:

int len = str.size();

下面来验证一下:

在这里插入图片描述

五、解决方案使用fgets代替 fgets(temp,sizeof(temp),stdin);

gets 已被弃用,因为它很危险,可能会导致缓冲区溢出。

解决方案

//接收用户输入,gets函数已经被弃用,这里替换成fgets函数,由于fgets函数会读入回车,这里将回车去掉 fgets(msg.data, sizeof(msg.data), stdin); //printf("%s", msg.data); msg.data[strlen(msg.data) - 1] = '\0'; //printf("-------------\n"); //printf("%s", msg.data); 回车与换行 一.知其然

\n是换行,英文是New line

\r是回车,英文是Carriage return

在这里插入图片描述

二.知其所以然

机械打字机有回车和换行两个键作用分别是:

换行就是把滚筒卷一格,不改变水平位置。

回车就是把水平位置复位,不卷动滚筒。

Enter = 回车+换行(\r\n) 注:\r\n连用时, 不能调换顺序

unix换行:\n(0x0A)

MAC回车:\r(0x0D)

WIN回车换行:\r\n(0x0D,0x0A)

关键字,操作符和函数区别 1:关键字

所谓关键字就是已被:语言本身使用, 不能作其它用途使用的字。

2:操作符

操作符是在表达式中用于连接不同对象的运算符,不同的操作符指定了不同的运算方式。

可以把操作符理解为语言内置的,最基础的函数,不可代替的函数!

操作符本质上也是函数。只是操作符是编译器需要进行进一步解释。

3:函数

函数是一组一起执行一个任务的语句

函数与操作符区别:

1:运算符只能重载,不能自定义,函数的名字随便起,只要是个标识符就行;但运算符不行。

2:函数本身有一段代码,程序执行时,遇到函数时,会先将函数的参数入栈,再跳到函数的代码来运行。而操作符则是在本地直接运算。

命令行参数argv

argv的类型为char **。它不是数组。它是指向char的指针。命令行参数存储在内存中,每个内存位置的地址存储在数组中。该数组是指向char的指针的数组。 argv指向此数组的第一个元素。

在这里插入图片描述

在大多数情况下,argv[0]表示程序名称,但是如果主机环境中无法使用程序名称,则argv[0][0]表示空字符。

原码补码

在这里插入图片描述

在这里插入图片描述

补码加法

题目 int i = -20; unsigned int j = 10; i + j ?

题解 答案为 -10

在这里插入图片描述

Linux C 语法分析 结构体指针类型函数

在这里插入图片描述

一个函数,函数返回值类型为结构体指针。 宏定义

在这里插入图片描述

#if 表达式    程序段1 #else   程序段2 #endif 表示:如果表达式为真,则编译程序段1,否则编译程序段2.

code中定义的是一些调试版本的代码,此时code完全被编译器忽略。如果想让code生效,只需把#if 0改成#if 1

#if 0还有一个重要的用途就是用来当成注释,如果你想要注释的程序很长,这个时候#if 0是最好的,保证不会犯错误。(但是林锐的书上说千万不要把#if 0 来当作块注释使用) #if 1可以让其间的变量成为局部变量。

这个结构表示你先前写好的code,现在用不上了,又不想删除,就用这个方法,比注释方便。

说白了#if 0/1 #endif 就相当于一段注释,改成1就不注释 改成0就是注释掉。

其他 __unregister_chrdev() 前面的 __ 一般代表函数为多重封装过的函数。

在这里插入图片描述

在这里插入图片描述

const语法整理

有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定。例如,用一个变量来表示班级的最大人数,或者表示缓冲区的大小。为了满足这一要求,可以使用const关键字对变量加以限定:

const int MaxNum = 100; //班级的最大人数

这样 MaxNum 的值就不能被修改了,任何对 MaxNum 赋值的行为都将引发错误:

MaxNum = 90; //错误,试图向 const 变量写入数据

我们经常将 const 变量称为常量(Constant)。创建常量的格式通常为:

const type name = value;

const 和 type 都是用来修饰变量的,它们的位置可以互换,也就是将 type 放在 const 前面:

type const name = value;

但我们通常采用第一种方式,不采用第二种方式。另外建议将常量名的首字母大写,以提醒程序员这是个常量。

由于常量一旦被创建后其值就不能再改变,所以==常量必须在定义的同时赋值(初始化),后面的任何赋值行为都将引发错误。==一如既往,初始化常量可以使用任意形式的表达式,如下所示:

#include int getNum(){ return 100; } int main(){ int n = 90; const int MaxNum1 = getNum(); //运行时初始化 const int MaxNum2 = n; //运行时初始化 const int MaxNum3 = 80; //编译时初始化 printf("%d, %d, %d\n", MaxNum1, MaxNum2, MaxNum3); return 0; }

运行结果: 100, 90, 80

一、const 和指针

const 也可以和指针变量一起使用,这样可以限制指针变量本身,也可以限制指针指向的数据。const 和指针一起使用会有几种不同的顺序,如下所示:

const int *p1; int const *p2; int * const p3;

在最后一种情况下,指针是只读的,也就是 p3 本身的值不能被修改;在前面两种情况下,指针所指向的数据是只读的,也就是 p1、p2 本身的值可以修改(指向不同的数据),但它们指向的数据不能被修改。

当然,指针本身和它指向的数据都有可能是只读的,下面的两种写法能够做到这一点:

const int * const p4; int const * const p5;

const 和指针结合的写法多少有点让初学者摸不着头脑,大家可以这样来记忆:const 离变量名近就是用来修饰指针变量的,离变量名远就是用来修饰指针指向的数据,如果近的和远的都有,那么就同时修饰指针变量以及它指向的数据。

二、const 和函数形参

在C语言中,单独定义 const 变量没有明显的优势,完全可以使用#define命令代替。const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,就可以用 const 来限制。

在C语言标准库中,有很多函数的形参都被 const 限制了,下面是部分函数的原型:

size_t strlen ( const char * str ); int strcmp ( const char * str1, const char * str2 ); char * strcat ( char * destination, const char * source ); char * strcpy ( char * destination, const char * source ); int system (const char* command); int puts ( const char * str ); int printf ( const char * format, ... );

我们自己在定义函数时也可以使用 const 对形参加以限制,例如查找字符串中某个字符出现的次数:

#include size_t strnchr(const char *str, char ch){ int i, n = 0, len = strlen(str); for(i=0; i n++; } } return n; } int main(){ char *str = "http://c.biancheng.net"; char ch = 't'; int n = strnchr(str, ch); printf("%d\n", n); return 0; }

运行结果:

3

根据 strnchr() 的功能可以推断,函数内部要对字符串 str 进行遍历,不应该有修改的动作,用 const 加以限制,不但可以防止由于程序员误操作引起的字符串修改,还可以给用户一个提示,函数不会修改你提供的字符串,请你放心。

三、const 和非 const 类型转换

当一个指针变量 str1 被 const 限制时,并且类似const char *str1这种形式,说明指针指向的数据不能被修改;如果将 str1 赋值给另外一个未被 const 修饰的指针变量 str2,就有可能发生危险。因为通过 str1 不能修改数据,而赋值后通过 str2 能够修改数据了,意义发生了转变,所以编译器不提倡这种行为,会给出错误或警告。

也就是说,const char *和char *是不同的类型,不能将const char *类型的数据赋值给char *类型的变量。但反过来是可以的,编译器允许将char *类型的数据赋值给const char *类型的变量。

这种限制很容易理解,**char 指向的数据有读取和写入权限,而const char 指向的数据只有读取权限,降低数据的权限不会带来任何问题,但提升数据的权限就有可能发生危险。

C语言标准库中很多函数的参数都被 const 限制了,但我们在以前的编码过程中并没有注意这个问题,经常将非 const 类型的数据传递给 const 类型的形参,这样做从未引发任何副作用,原因就是上面讲到的,将非 const 类型转换为 const 类型是允许的。

下面是一个将 const 类型赋值给非 const 类型的例子:

#include void func(char *str){ } int main(){ const char *str1 = "c.biancheng.net"; char *str2 = str1; func(str1); return 0; }

第7、8行代码分别通过赋值、传参(传参的本质也是赋值)将 const 类型的数据交给了非 const 类型的变量,编译器不会容忍这种行为,会给出警告,甚至直接报错。

switch和if else的比较

if-else

只是单纯地一个接一个比较;if…else每个条件都计算一遍;

switch

使用了Binary Tree算法;绝大部分情况下switch会快一点,除非是if-else的第一个条件就为true编译器编译switch与编译if…else…不同。不管有多少case,都直接跳转,不需逐个比较查询;switch只计算一次值,然后都是test , jmp,

有很多else if的时候,用switch case比较清晰。==switch使用查找表的方式决定了case的条件必须是一个连续的常量。==而if-else则可以灵活的多。对于switch语句来说,起实际是使用一个跳转表实现分支结构,不需要一次进行比较每一个所需要的条件。进行比较的次数为1.但是对于if…else语句来说:最少的比较次数为1,跟switch相比,在时间方面,switch语句的执行速度比if else要快,但是在程序执行占用的空间方面,switch语句需要一张跳转表来维护。这个跳转,表的本质是一个拥有标号的数组,需要额外的存储空间,if else语句的空间效率更好一点。switch是一个很典型的空间换时间的例子。但是switch只能判断是一个指定值的数据,而不能对一个区间中的数据进行判断。这时候选择if…else语句是一个很好的选择。

switch case与if else的效率问题

switch case与if else的区别:switch case会生成一个跳转表来指示实际的case分支的地址,而if…else却需要遍历条件分支直到命中条件。

switch case的优缺点

switch case的优点: 当分支较多时,用switch的效率是很高的。因为switch是确定了选择值之后直接跳转到那个特定的分支. switch case的缺点: switch…case占用较多的代码空间,因为它要生成跳表,特别是当case常量分布范围很大但实际有效值又比较少的情况,switch…case的空间利用率将变得很低。switch…case只能处理case为常量的情况。

if else的优缺点

if else的优点:if else能应用于更多的场所以if else比较灵活。if else的缺点:if else必须遍历所以的可能值。

总结

在选择分支较多时,选用switch…case结构会提高程序的效率,但switch不足的地方在于只能处理字符或者数字类型的变量,if…else结构更加灵活一些,if…else结构可以用于判断表达式是否成立,比如if(a+b>c),if…else的应用范围更广,switch…case结构在某些情况下可以替代if…else结构。

#pragma once 1、#pragma once有什么作用?

为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。

在能够支持这两种方式的编译器上,二者并没有太大的区别。但两者仍然有一些细微的区别。

2、两者的使用方式有何区别

示例代码如下:

//方式一: #ifndef __SOMEFILE_H__ #define __SOMEFILE_H__ ... ... // 声明、定义语句 #endif //方式二: #pragmaonce ... ... // 声明、定义语句 3、两者各有何特点 (1)#ifndef

#ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。

当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。

由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。

(2)#pragma once

#pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。

你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。

其好处是,你不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。

对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。

另外,这种方式不支持跨平台!

4、两者之间有什么联系?

#pragma once 方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受C/C++语言标准的支持,不受编译器的任何限制;

而**#pragma once方式却不受一些较老版本的编译器支持**,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。

一般而言,当程序员听到这样的话,都会选择#ifndef方式,为了努力使得自己的代码“存活”时间更久,通常宁愿降低一些编译性能,这是程序员的个性,当然这是题外话啦。

还看到一种用法是把两者放在一起的:

#pragma once #ifndef __SOMEFILE_H__ #define __SOMEFILE_H__ ... ... // 声明、定义语句 #endif

总结:

看起来似乎是想兼有两者的优点。不过只要使用了#ifndef就会有宏名冲突的危险,也无法避免不支持#pragma once的编译器报错,所以混用两种方法似乎不能带来更多的好处,倒是会让一些不熟悉的人感到困惑。

选择哪种方式,应该在了解两种方式的情况下,视具体情况而定。只要有一个合理的约定来避开缺点,我认为哪种方式都是可以接受的。而这个已经不是标准或者编译器的责任了,应当由程序员自己或者小范围内的开发规范来搞定。

方式一由语言支持所以移植性好,方式二 可以避免名字冲突。

逗号表达式 优先级最低从左到右逐个计算逗号表达式作为一个整体,它的值为最后一个表达式的值x = (++i, i++, i+10); 逗号表达式中,i 在遇到每个逗号后,认为本计算单位已经结束,i 这时候自加。 C 嵌入式代码整理 获取键盘输入 scanf("%d",&cmd); getchar(); //加一个getchar()防止输入乱码,程序跑飞 为结构体内元素单独赋值(Linux中)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oi2I8KW1-1689595512714)(assets/1960_1.png)]

static struct file_operations pin4_fops = { .owner = THIS_MODULE, .open = pin4_open, //.a = 1, .b = 2, .write = pin4_write }; 字符串拷贝输出 size_t readData(void *ptr, size_t size, size_t nmemb, void *stream) { char buf[1024] = {'\0'}; strncpy(buf, ptr, 1024); printf("==========================================="); printf("%s\n",buf); } 程序运行系统功能 #include int main(int argc,char **argv) { int time = atoi(argv[1]); for(int i=0;i int i = 0; for(i=0;i int age; } int main() { struct Student *p; p = malloc(sizeof(struct Student)); //需要分配空间 p->age; }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3