手机wap网站模板,营站快车代理平台,现在房子装修流行什么风格,淘宝客返利网站建设在之前我们学习了C语言和初阶数据结构的相关知识#xff0c;现在已经有了一定的代码能力和对数据结构也有了基础的认识#xff0c;接下来我们将进入到新的专题当中#xff0c;这个专题就是C。在C中我们需要花费更大的精力和更长的时间去学习这门建立在C语言基础之上的计算机…在之前我们学习了C语言和初阶数据结构的相关知识现在已经有了一定的代码能力和对数据结构也有了基础的认识接下来我们将进入到新的专题当中这个专题就是C。在C中我们需要花费更大的精力和更长的时间去学习这门建立在C语言基础之上的计算机语言相信通过接下C的学习能让我们对计算机有更深入的了解。在本篇中将会介绍C这门语言的由来和重要性以及了解在C语言中不存在但在C中特有的基础知识点一起加油吧 1.C简介
1.1 C的发展历史
C是一种由Bjarne Stroustrup在20世纪80年代初设计并发展的通用、静态类型的、编译式的、大小写敏感的、支持多种程序范式如过程化、面向对象、泛型的高级编程语言。它起源于C语言最初是为了改进C而设计的因此保留了C的基本语法结构并在此基础上增加了更多的特性。 C的起源可以追溯到1979年当时Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)在贝尔实验室从事计算机科学和软件工程的研究工作。⾯对项目中复杂的软件开发任务特别是模拟和操作系统的开发工作他感受到了现有语言如C语言在表达能力、可维护性和可扩展性方面的不足。 1983年Bjarne Stroustrup在C语⾔的基础上添加了面向对象编程的特性设计出了C语言的雏形此时的C已经有了类、封装、继承等核心概念为后来的⾯向对象编程奠定了基础。这⼀年该语言被正式命名为C。在随后的⼏年中C在学术界和工业界的应用逐渐增多。⼀些大学和研究所开始将C作为教学和研究的⾸选语言而⼀些公司也开始在产品开发中尝试使用C。这⼀时期C的标准库和模板等特性也得到了进⼀步的完善和发展。C的标准化工作于1989年开始并成立了一个ANSI和ISOInternational StandardsOrganization国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第⼀个标准化草案。在该草案中委员会在保持斯特劳斯特卢普最初定义的所有特征的同时还增加了部分新特征。在完成C标准化的第⼀个草案后不久STLStandard Template Library是惠普实验室开发的⼀系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来的。在通过了标准化第⼀个草案之后联合标准化委员会投票并通过了将STL包含到C标准中的提议。STL对C的扩展超出C的最初定义范围。虽然在标准中增加STL是个很重要的决定但也因此延缓了C标准化的进程。 1997年11⽉14日联合标准化委员会通过了该标准的最终草案。1998年C的ANSI/IS0标准被投入使用。 1.2 C版本更新 1.3 C的重要性
2024年8月TIOBE指数 在TIOBE发布的编程语言最新排行榜中我们可以看到C排在第二位这说明C在当前还在被广泛的使用这就就表明C对我们还是非常重要的
1.4 C在工作领域中的应用
C应用领域非常的广泛服务器端、游戏引擎、机器学习引擎、音视频处理、嵌入式软件、电信设备、金融应用、基础库、操作系统、编译器、基础架构、基础工具、硬件交互等很多方面都有。
1.5C参考文档
在学习C过程中参考文档对我们的学习非常重要在学习过程中要试着去通过文档来获取相关的信息以下是几个参考文档的网址
Reference - C Reference
C 参考手册 - cppreference.com
cppreference.com 2.C基础知识点
2.1C的第一个程序
在之前C语言中我们在编写程序时程序的源文件是以.c结尾的但在C中与C语言有所不同C的源文件是以.cpp结尾的 在之前的C语言中要在屏幕上打印一个Hello world我们会用以下代码来实现,其实在C中也是可以按照以下方式来输出的原因是C兼容C语⾔绝⼤多数的语法
#includestdio.h
int main()
{printf(Hello world\n);return 0;
}
在C中则会变为以下形式
#includeiostreamint main()
{std::cout Hello world std::endl;return 0;
}
这时你可能会看不懂C中的这种输出方法那么接下来就让我们来一点点了解这其中所包含的知识吧相信通过这些你在本篇结束后就会觉得上面的代码易如反掌了
2.2命名空间
2.2.1.namespace的意义和价值
#includestdio.h
#includestdlib.hint rand 10;
int main()
{printf(%d\n,rand);return 0;
}
在以上代码中看起来没问题但是运行之后就会出现以下的报错这是为什么呢
这时因为在以上代码中我们引用了头文件stdlib.h我们知道头文件在预处理时会被展开所以在以上我们定义的变量rand其实在预处理之后就已经定义了这时在运行代码时就会出现重定义的问题。那么怎么才能解决这个问题呢 在C中就引入了命名空间使用命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染namespace关键字的出现就是针对这种问题的。
2.2.2 命名空间的定义
先来看以下的定义 • 定义命名空间需要使用到namespace关键字后面跟命名空间的名字然后接⼀对{}即可{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。 • namespace本质是定义出⼀个域这个域跟全局域各自独立不同的域可以定义同名变量所以下面的rand不在冲突了。 • C中域有函数局部域全局域命名空间域类域域影响的是编译时语法查找⼀个量/函数/类型出处(声明或定义)的逻辑所有有了域隔离名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑还会影响变量的生命周期命名空间域和类域不影响变量生命周期。 有了命名空间就可以解决以上的问题了例如以下代码
#includestdio.h
#includestdlib.h//命名空间的名字是没有限定的可以自己来起
namespace zhz
{int rand10;
}int main()
{
// 这⾥指定zhz命名空间中的randprintf(%d\n, zhz::rand);
// 这⾥默认是访问的是全局的rand函数指针printf(%p\n, rand);return 0;
}注以上的::在C中表示空间域操作符::之前是空间域之后是空间域中的变量
接下来来看这条定义 • namespace只能定义在全局当然他还可以嵌套定义。 例如以下就是namespace的嵌套定义
#includestdio.h
#includestdlib.hnamespace zhz
{int rand10;namespace ys{int x5;double y6.6;}}int main()
{printf(%d\n, zhz::rand);printf(%d\n, zhz::ys::x);printf(%d\n, zhz::ys::y);return 0;
}
命名空间还有以下的定义 • 项目工程中多文件中定义的同名namespace会认为是⼀个namespace不会冲突。• C标准库都放在⼀个叫std(standard)的命名空间中。 2.2.3 命名空间的使用
编译查找⼀个变量的声明/定义时默认只会在局部或者全局查找不会到命名空间里面去查找。所以下面程序会编译报错。
#includestdio.h
#includestdlib.hnamespace zhz
{int a10;
}int main()
{printf(%d\n, a);return 0;
}所以我们要使用命名空间中定义的变量/函数有三种方式
1•指定命名空间访问项目中推荐这种方式。
#includestdio.h
#includestdlib.hnamespace zhz
{int a10;
}int main()
{printf(%d\n, zhz::a);return 0;
}2• using将命名空间中某个成员展开项目中经常访问的不存在冲突的成员推荐这种方式。
#includestdio.h
#includestdlib.hnamespace zhz
{int a10;
}
using zhz::a;int main()
{printf(%d\n, a);return 0;
}3• 展开命名空间中全部成员项目不推荐冲突风险很大日常小练习程序为了方便推荐使用。
#includestdio.h
#includestdlib.hnamespace zhz
{int a10;
}
using namespace zhz;int main()
{printf(%d\n, a);return 0;
}2.3C输入和输出 在学习C中的输入与输出前我们需先了解iostream的概念• iostream 是 Input Output Stream 的缩写是标准的输入、输出流库定义了标准的输入、输出对象。 接下来就可以来了解C中的输入与输出了
• std::cin 是 istream 类的对象它主要面向窄字符narrow characters (of type char)的标准输 入流。 • std::cout 是 ostream 类的对象它主要面向窄字符的标准输出流。 • std::endl 是一个函数流插入输出时相当于插入一个换行字符加刷新缓冲区。 • cout/cin/endl等都属于C标准库C标准库都放在一个叫std(standard)的命名空间中所以要 通过命名空间的使用方式去用他们。
注以上提到的对象和C语言中的变量是相同的在C中使用对象和变量来描述都是可以的只不过在C中最好还是用对象 。
注在内存当中才有整型、字符等不同类型的概念在其他的设备中只支持字符因此在std::cout在输出过程中在要将其他类型都转换为字符才输出std::cin在读取外部设备时也是先读取到字符之后再转换为其他类型再输入到程序当中。 除了以上的概念再了解C的输入输出还要了解以下的两个运算符 • 是流插入运算符是流提取运算符。C语言还用这两个运算符做位运算左移/右移 例如以下的示例中使用scanf和printf与std::cin和std::cout实现的功能是相同的在以上中能使用cin和cout是因为使用了using namespace std展开std但在实际项目开发中不建议using namespace std。
这⾥我们没有包含stdio.h也可以使⽤printf和scanf在包含iostream间接包含了。
vs系列编译器是这样的其他编译器可能会报错。
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostreamint main()
{ int i 0;double j 0.0;scanf(%d %lf, i, j);printf(%d %0.1lf\n, i, j);cin i j;cout i j endl;return 0;
} 2.4缺省参数
概念
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时如果没有指定实参 则采用该形参的缺省值否则使用指定的实参缺省参数分为全缺省和半缺省参数。有些地方把 缺省参数也叫默认参数
全缺省就是全部形参给缺省值例如以下的函数参数就为全缺省
#include iostream
using namespace std;
// 全缺省
void Func1(int a 10, int b 20, int c 30)
{cout a a endl;cout b b endl;cout c c endl;
} 半缺省就是部分形参给缺省值例如以下的函数参数就为半缺省注C规定半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值。
#include iostream
using namespace std;
// 半缺省
void Func2(int a , int b 20, int c 30)
{cout a a endl;cout b b endl;cout c c endl;
}void Func3(int a , int b , int c 30)
{cout a a endl;cout b b endl;cout c c endl;
}
同时在C中规定带缺省参数的函数调用C规定必须从左到右依次给实参不能跳跃给实参
#include iostream
using namespace std;
// 全缺省
void Func1(int a 10, int b 20, int c 30)
{cout a a endl;cout b b endl;cout c c endl;
}
void Func2(int a , int b 20, int c 30)
{cout a a endl;cout b b endl;cout c c endl;
}void Func3(int a , int b , int c 30)
{cout a a endl;cout b b endl;cout c c endl;
}
int main()
{Func1();Func1(10);Func1(20,30);Func2(10);Func3(60,70);return 0;
}
有了缺少参数在一些示例当中就会有很大的用处例如之前实现栈中就可以在初始化函数中使用到缺少参数这样就可以在初始化时不知应开辟多少空间时就为栈开辟4字节的空间
但在使用中还要一个要注意的点是函数声明和定义分离时缺省参数不能在函数声明和定义中同时出现规定必须函数声明给缺省值。
// Stack.h
#include iostream
#include assert.h
using namespace std;
typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}Stack;
void STInit(Stack* ps, int n 4);// Stack.cpp
#includeStack.h
// 缺省参数不能声明和定义同时给
void STInit(Stack* ps, int n)
{assert(ps n 0);ps-a (STDataType*)malloc(n * sizeof(STDataType));ps-top 0;ps-capacity n;
} 2.5函数重载
C支持在同⼀作用域中出现同名函数但是要求这些同名函数的形参不同可以是参数个数不同或者类型不同。这样C函数调⽤就表现出了多态行为使用更灵活。C语言是不支持同⼀作用域中出现同名函数的。
以下三种形式都为函数重载1.参数类型不同
#includeiostream
using namespace std;int Add(int left, int right)
{cout int Add(int left, int right) endl;return left right;
}
double Add(double left, double right)
{cout double Add(double left, double right) endl;return left right;
}
2.参数个数不同
#includeiostream
using namespace std;void f()
{cout f() endl;
}
void f(int a)
{cout f(int a) endl;
}
3.参数类型顺序不同
#includeiostream
using namespace std;void f(int a, char b)
{cout f(int a,char b) endl;
}
void f(char b, int a)
{cout f(char b, int a) endl;
}
要注意的是参数的返回值不同不能作为是否是函数重载的判断条件因为在调用是无法区分
在两个函数中若一个没有参数另一个参数为缺少参数虽然也为函数重载但在调用函数时如果不传参就会不知道应调用那个函数就会出现歧义 例如以下两个函数
#includeiostream
using namespace std;void f1()
{cout f() endl;
}
void f1(int a 10)
{cout f(int a) endl;
}
int main()
{ f1();return 0;
} 2.6引用
2.6.1引用的概念
引⽤不是新定义⼀个变量而是给已存在变量取了一个别名编译器不会为引用变量开辟内存空间它和它引用的变量共用同⼀块内存空间。 引用就像在我们生活中就像是一个人的外号就比如水浒传中的林冲外号就为“豹子头”李逵外号就是“黑旋风”。 在C中的引用使用的方法是 类型 引用别名 引用对象;
注C中为了避免引入太多的运算符会复用C语言的⼀些符号在此的和指针中表示的取地址不同也和运算符中的表示的按位与不同
#includeiostreamusing namespace std;int main()
{int a 0;// 引⽤b和c是a的别名int b a;int c a;// 也可以给别名b取别名d相当于还是a的别名int d b;d;// 这⾥取地址我们看到是⼀样的cout a endl;cout b endl;cout c endl;cout d endl;cout a endl;return 0;
} 以上代码输出结果就可以发现abcd的地址都是一样的这说明都指向同一内存空间
2.6.2引用的特性 引用会有以下的特征:1.引用在定义时必须初始化2.一个变量可以有多个引用3.引用一旦引用⼀个实体再不能引用其他实体 2.6.3引用的使用
引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被 引用对象。 例如在以下代码中就是使用引用来实现两个数的交换在此就比指针更简洁一些 #includeiostream
using namespace std;
void Swap(int rx, int ry)
{int tmp rx;rx ry;ry tmp;
}
int main()
{int x 0, y 1;cout x y endl;Swap(x, y);cout x y endl;return 0;
} 引用传参跟指针传参功能是类似的引用传参相对更方便⼀些就比如在之前学习过的单链表中在实现单链表的各函数中因为要改变实参所以要用到二级指针代码就会看上去比较难理解但在C中就可以使用引用就可以让代码更易懂
typedef struct ListNode
{int val;struct ListNode* next;
}LTNode, *PNode;
// 指针变量也可以取别名这⾥LTNode* phead就是给指针变量取别名
// 这样就不需要⽤⼆级指针了相对⽽⾔简化了程序
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode* phead, int x)
void ListPushBack(PNode phead, int x)
{PNode newnode (PNode)malloc(sizeof(LTNode));newnode-val x;newnode-next NULL;if (phead NULL){phead newnode;}else{//...}
}
但注意虽然在一些场景下引用是要比指针更好用的但引用是无法完全取代指针的这是因为引用的特性引用一旦引用⼀个实体再不能引用其他实体。就比如在单链表中如果将指向下一个结点的指针改为下一个结点名的引用这样在删除中就会出现问题了。
2.6.4const引用
在之前C语言指针中学习了const变量还有const修饰指针等的概念其实还存在const引用首先来了解const在使用中的要求const引用也可以引用普通对象因为对象的访问权限在引用过程中可以缩小但是不能放大。
例如以下代码
#includeiostreamusing namespace std;int main()
{int a 10;const int b a;const int c 20;//int d c;
//正确方式const int dc;return 0;
}
在c的引用时如果使用int dc就会出现权限的放大这时程序就会出现以下报错因此正确的写法是const int dc 再来看以下代码为什么在rb和rd之前都要加const呢这就需要我们在来了解const引用的其他特性int rb a*3; double d 12.34; int rd d; 这样⼀些场景下a*3的结果保存在⼀个临时对象中 int rd d 也是类似在类型转换中会产生临时对象存储中间值也就是时rb和rd引用的都是临时对象而C规定临时对象具有常性所以这里就触发了权限放大必须要用常引用才可以。
(所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象C中把这个未命名对象叫做临时对象)
#includeiostreamusing namespace std;
int main()
{int a 10;const int rb a*3;double d 12.34;const int rd d;return 0;
} 2.6.5指针和引用的关系
在了解完引用后来看看和指针究竟有哪些区别吧 • 语法概念上引用是⼀个变量的取别名不开空间指针是存储⼀个变量地址要开空间。 • 引用在定义时必须初始化指针建议初始化但是语法上不是必须的。 • 引用在初始化时引用⼀个对象后就不能再引⽤其他对象而指针可以在不断地改变指向对象。 • 引用可以直接访问指向对象指针需要解引用才是访问指向对象。 • sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节64位下是8byte) • 指针很容易出现空指针和野指针的问题引用很少出现引用使用起来相对更安全⼀些。 2.7 inline
⽤inline修饰的函数叫做内联函数编译时C编译器会在调用的地方展开内联函数这样调用内联函数就需要建立栈帧了就可以提高效率 但inline对于编译器而言只是⼀个建议也就是说你加了inline编译器也可以选择在调用的地方不展开不同编译器关于inline什么情况展开各不相同因为C标准没有规定这个。inline适用于频繁调用的短小函数对于递归函数代码相对多⼀些的函数加上inline也会被编译器忽略。
在此有了inline就可以来替代宏C语言实现宏函数也会在预处理时替换展开但是宏函数实现很复杂很容易出错的且不方便调试C设计了inline目的就是替代C的宏函数
注意inline不建议声明和定义分离到两个文件分离会导致链接错误。因为inline被展开就没有函数地址链接时会出现报错。 我们可以通过反汇编来看展开之后是上面样的例如以下代码
#includeiostream
using namespace std;
inline int Add(int x, int y)
{int ret x y;ret 1;ret 1;ret 1;return ret;
}
int main()
{
// 可以通过汇编观察程序是否展开
// 有call Add语句就是没有展开没有就是展开了int ret Add(1, 2);cout Add(1, 2) * 5 endl;return 0;
} vs编译器 debug版本下面默认是不展开inline的这样方便调试debug版本想展开需要设置⼀下以下两个地方。 在未进行以上操作时反汇编是以下的形式
改了之后就可以来看反汇编之后没有了call Add这就说明Add展开了 2.8 nullptr
NULL实际是一个宏在传统的C头文件(stddef.h)中可以看到如下代码
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif
C中NULL可能被定义为字面常量0或者C中被定义为无类型指针(void*)的常量。不论采取何种 定义在使用空值的指针时都不可避免的会遇到⼀些麻烦本想通过f(NULL)调用指针版本的 f(int*)函数但是由于NULL被定义成0调用了f(int x)因此与程序的初衷相悖。f((void*)NULL); 调用会报错。
#includeiostream
using namespace std;
void f(int x)
{cout f(int x) endl;
}
void f(int* ptr)
{cout f(int* ptr) endl;
}
int main()
{f(0);
// 本想通过f(NULL)调⽤指针版本的f(int*)函数但是由于NULL被定义成0调⽤了f(int
x)因此与程序的初衷相悖。f(NULL);f((int*)NULL);
// 编译报错error C2665: “f”: 2 个重载中没有⼀个可以转换所有参数类型
// f((void*)NULL);f(nullptr);return 0;
}
为了解决以上的不足C11中引入nullptrnullptr是⼀个特殊的关键字nullptr是⼀种特殊类型的字面量它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的题因为nullptr只能被隐式地转换为指针类型而不能被转换为整数类型。