下篇:【C 篇】启航——初识C (下篇)
引言
在编程语言的浩瀚海洋中,C 以其强大的功能和灵活性脱颖而出。自1979年由Bjarne Stroustrup在贝尔实验室开发以来,C 逐渐成为高性能应用和系统编程的首选语言。它不仅继承了C语言的高效特性,还引入了面向对象编程的概念,使得软件开发更具结构性和可维护性。
C 的广泛应用涵盖了从游戏开发、图形处理到操作系统和大型软件系统的构建。凭借丰富的标准库和强大的模板机制,C 使开发者能够编写高效且可复用的代码。这篇博客将带你深入了解C 的基本概念、发展历程,以及学习这门语言的重要性和应用场景。无论你是编程新手还是有经验的开发者,C 都能为你的编程之旅增添新的视角和技能。
一、C 的起源和发展史
1.起源
C 的起源可以追溯到1979年,由Bjarne Stroustrup在贝尔实验室开发。他在面对复杂软件开发时,意识到现有语言(如C语言)的局限性,于是于1983年在C语言基础上引入了面向对象的特性,正式命名为C 。该语言逐渐在学术界和工业界获得应用,并成为许多大学的教学语言。
2.C 版本更新
1989年,C 的标准化工作启动,1994年提出首个标准草案,其中引入了标准模板库(STL),极大丰富了C 的功能。经过多次讨论,1998年ANSI/ISO标准正式发布,奠定了C 在编程语言中的重要地位。之后,C 经历了多次更新,如C 11、C 14、C 17和C 20,持续演进以适应现代开发需求。
二、C 在⼯作领域中的应⽤
C 的应⽤领域服务器端、游戏(引擎)、机器学习引擎、⾳视频处理、嵌⼊式软件、电信设备、⾦融应⽤、基础库、操作系统、编译器、基础架构、基础⼯具、硬件交互等很多⽅⾯都有。 1. ⼤型系统软件开发。如编译器、数据库、操作系统、浏览器等等 2. ⾳视频处理。常⻅的⾳视频开源库和⽅案有FFmpeg、WebRTC、Mediasoup、ijkplayer,⾳视频开发最主要的技术栈就是C 。 3. PC客⼾端开发。⼀般是开发Windows上的桌⾯软件,⽐如WPS之类的,技术栈的话⼀般是C 和QT,QT 是⼀个跨平台的 C 图形⽤⼾界⾯(Graphical User Interface,GUI)程序。 4. 服务端开发。各种⼤型应⽤⽹络连接的⾼并发后台服务。这块Java也⽐较多,C 主要⽤于⼀些对 性能要求⽐较⾼的地⽅。如:游戏服务、流媒体服务、量化⾼频交易服务等 5. 游戏引擎开发。很多游戏引擎就都是使⽤C 开发的,游戏开发要掌握C 基础和数据结构,学习图形学知识,掌握游戏引擎和框架,了解引擎实现,引擎源代码可以学习UE4、Cocos2d-x等开源 引擎实现 6. 嵌⼊式开发。嵌⼊式把具有计算能⼒的主控板嵌⼊到机器装置或者电⼦装置的内部,通过软件能够控制这些装置。⽐如:智能⼿环、摄像头、扫地机器⼈、智能⾳响、⻔禁系统、⻋载系统等等,粗略⼀点,嵌⼊式开发主要分为嵌⼊式应⽤和嵌⼊式驱动开发。 7. 机器学习引擎。机器学习底层的很多算法都是⽤C 实现的,上层⽤python封装起来。如果你只想准备数据训练模型,那么学会Python基本上就够了,如果你想做机器学习系统的开发,那么需要学会C 。 8. 测试开发/测试。每个公司研发团队,有研发就有测试,测试主要分为测试开发和功能测试,测试开发⼀般是使⽤⼀些测试⼯具(selenium、Jmeter等),设计测试⽤例,然后写⼀些脚本进⾏⾃动化测试,性能测试等,有些还需要⾃⾏开发⼀些测试⽤具。功能测试主要是根据产品的功能,设计测试⽤例,然后⼿动的⽅式进⾏测试。
三、C 入门建议
1.参考文档
https://zh.cppreference.com/w/cpp https://legacy.cplusplus.com/reference/ https://en.cppreference.com/w/ 说明:第⼀个链接不是C 官⽅⽂档,标准也只更新到C 11,但是以头⽂件形式呈现,内容⽐较易看好懂。后两个链接分别是C 官⽅⽂档的中⽂版和英⽂版,信息很全,更新到了最新的C 标准,但是相⽐第⼀个不那么易看;⼏个⽂档各有优势,我们结合着使⽤。
2.推荐书籍
- C Primer:主要讲解语法,经典的语法书籍,前后中期都可以看,前期如果⾃学看可能会有点晦涩难懂,能看懂多少看懂多少,就当预习,学了⽐特课程后,中后期作为语法字典,⾮常好⽤。
- STL源码剖析:主要从底层实现的⻆度结合STL源码,庖丁解⽜式剖析STL的实现,是侯捷⽼师的经典之作。可以很好的帮助我们学习别⼈⽤语法是如何实现出⾼效简洁的数据结构和算法代码,如何使⽤泛型封装等。让我们不再坐井观天,闭⻔造⻋,本书课程上⼀半以后,中后期可以看。
- Effctive C :本书也是侯捷⽼师翻译的,本书有的⼀句评价,把C 程序员分为看过此书的和没看过此书的。本书主要讲了55个如何正确⾼效使⽤C 的条款,建议中后期可以看⼀遍,⼯作1-2年后再看⼀遍,相信会有不⼀样的收获。
四、C 的第一个程序
1.C语言写法
C 兼容C语⾔绝⼤多数的语法,所以C语⾔实现的hello world依旧可以运⾏,C 中需要把定义⽂件代码后缀改为.cpp,vs编译器看到是.cpp就会调⽤C 编译器编译,linux下要⽤g 编译,不再是gcc。
代码语言:javascript复制#include<stdio.h>
int main()
{
printf("hello world!");
return 0;
}
当然C 有⼀套⾃⼰的输⼊输出,严格说C 版本的hello world应该是这样写的。
2.C 写法
代码语言:javascript复制#include<iostream>
int main() {
std::cout << "hello world" << std::endl;
return 0;
}
五、命名空间
1.为什么要有命名空间
在C/C 中,变量、函数和后⾯要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全局作⽤域中,可能会导致很多冲突。 使⽤命名空间的⽬的是对标识符的名称进⾏本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
2.定义命名空间
命名空间(namespace)是C 中用于组织代码的一种机制。它允许将相关的类、函数、变量等放在一个命名空间中,以避免名称冲突和提高代码的可读性。它通过关键字namespace定义,基本语法如下:
代码语言:javascript复制namespace NamespaceName {
// 这里可以定义变量、函数、类型等
int variable;
void function() {
// 函数实现
}
}
在上面的例子中,我们定义了一个名为NamespaceName的命名空间,包含一个整数变量和一个函数。
3.主要特点
域的独立性:命名空间创建了一个独立的作用域,在这个作用域中定义的成员与全局作用域及其他命名空间的成员互不干扰。
解决命名冲突:不同的命名空间可以定义同名的变量、函数等,而不会发生冲突。例如,namespace A { void func(); }
和 namespace B { void func(); }
可以同时存在。
可嵌套:命名空间可以嵌套定义,以更好地组织代码。例如:
代码语言:javascript复制namespace Outer {
namespace Inner {
void innerFunction() {
// 实现
}
}
}
跨文件共享:在项目中可以在多个文件中定义同名的命名空间,它们会被视为同一个命名空间,不会冲突。
标准库的命名空间:C 标准库中的所有元素都包含在std
命名空间中,例如std::cout
和std::vector
。
4.使用示例
要使用命名空间中的成员,可以通过以下方式:
使用作用域运算符:
代码语言:javascript复制NamespaceName::function();
使用using声明:
代码语言:javascript复制using namespace NamespaceName;
function(); // 直接调用
注:using展开命名空间中全部成员,项⽬不推荐,冲突⻛险很⼤,⽇常⼩练习程序为了⽅便推荐使⽤。也可以用using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
六、C 输⼊&输出
1.概念介绍
• <iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。 • std::cin 是 istream 类的对象 ,它主要⾯向窄字符(narrow characters (of type char))的标准输⼊流。 • std::cout 是 ostream 类的对象,它主要⾯向窄字符的标准输出流。 • std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。 • <<是流插⼊运算符,>>是流提取运算符。 (C语⾔还⽤这两个运算符做位运算左移/右移) • 使⽤C 输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C 的输⼊输出可以⾃动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是 C 的流能更好的⽀持⾃定义类型对象的输⼊输出 。 • IO流涉及类和对象,运算符重载、继承等很多⾯向对象的知识,这些知识我们还没有讲解,所以这⾥我们只能简单认识⼀下C IO流的⽤法,后⾯我们会有专⻔的⼀个章节来细节IO流库。 • cout/cin/endl等都属于C 标准库,C 标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们。 • ⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议using namespace std。 • 这⾥我们没有包含<stdio.h>,也可以使⽤printf和scanf,在包含<iostream>间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
2代码示例
代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
int main()
{
// 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码 可以提⾼C IO效率
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int a = 10;
double b = 1.1;
char c = 'a';
cout << a << " " << b << " " << c << endl;
std::cout << a << " " << b << " " << c << std::endl;
scanf_s("%d%lf", &a, &b);
printf("%d %lfn", a, b);
//可以自动识别变量类型
cin >> a >> b >> c;
cout << a << " " << b << " " << c << endl;
return 0;
}
七、缺省参数
1.定义
C 中的缺省参数(Default Parameters)是指在函数声明或定义时为参数指定的默认值。如果在函数调用时没有为这些参数提供实参,那么这些参数将自动使用默认值。这种特性可以提高函数的灵活性和易用性。以下是关于C 缺省参数的详细介绍:
缺省参数的规则:
- 缺省参数分为全缺省和半缺省参数。
- 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C 规定半缺省参数必须从右往左 依次连续缺省,不能间隔跳跃给缺省值。
- 带缺省参数的函数调⽤,C 规定必须从左到右依次给实参,不能跳跃给实参。
- 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
2.代码示例
代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS
#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 << endl;
}
// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();//等同于 func1(10, 20, 30)
Func1(1);//等同于 func1(1, 20, 30)
Func1(1, 2);//等同于 func1(1, 2, 30)
Func1(1, 2, 3);//等同于 func1(1, 2, 3)
Func2(100);//等同于 func2(100, 10, 20)
Func2(100, 200);//等同于 func2(100, 200, 20)
Func2(100, 200, 300);//等同于 func2(100, 200, 300)
return 0;
}
八、函数重载
1.函数重载的基本概念
在C 中,函数重载(Function Overloading)是一种特性,它允许我们使用相同的函数名定义多个函数,但这些函数的参数列表(参数的数量、类型或顺序)必须不同。编译器会根据传递给函数的参数来决定调用哪个函数。
函数重载的核心是参数列表的不同。下面是一些重载函数的例子:
代码语言:javascript复制void print(int num);
void print(double num);
void print(const char* str);
在上面的例子中,我们定义了三个名为print的函数,但它们的参数列表不同,因此它们被视为不同的函数。
2.函数重载的规则
- 参数数量不同:函数可以有不同的参数数量。
- 参数类型不同:函数可以有相同数量但不同类型的参数。
- 参数顺序不同:如果参数数量相同,参数类型也相同,但参数的顺序不同,也可以构成重载。
3代码示例
代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//1.参数类型不同
int Add(int x, int y)
{
cout << "int Add(int x, int y)" << endl;
return x y;
}
double Add(double x, double y)
{
cout << "double Add(double x, double y)" << endl;
return x y;
}
//2.参数个数不同
void fun()
{
cout << "void fun()" << endl;
}
void fun(int a)
{
cout << "void fun(int a)" << endl;
}
//3.参数类型顺序不同
void func(int a, double b)
{
cout << "void func(int a, double b)" << endl;
}
void func(double a, int b)
{
cout << "void func(double a, int b))" << endl;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
fun();
fun(10);
func(10, 2.2);
func(10.1, 22);
return 0;
}