【C++深学日志】从0开始的C++生活

对于许多初学者而言,C++的复杂性或许会让人望而却步,但请相信,只要掌握了正确的方法,入门并精通它并非难事,学习C++就像是学习与计算机进行高效对话,它不仅能帮助你理解底层的计算机原理,更能为你打开一扇通往高性能软件开发的大门。为了系统地记录和分享我的C++学习历程、实践心得以及对现代C++新特性的探索,我决定开始创作这个C++专栏​,专栏将遵循技术写作的一些最佳实践,力求清晰易懂。那么和我一起开始C++学习之旅吧!

1.C++的发展历程

正所谓吃水不忘挖井人,学习一门语言,我们要先了解它的发展历程,它不仅是一部技术演进史,更反映了软件工程理念的变迁。

第一阶段:前标准阶段(1979-1998)

  1. 1979年:Bjarne Stroustrup在贝尔实验室开始工作,为了分析UNIX内核的分布式系统,他认为需要一种既像C语言一样高效,能直接操作于硬件,又支持像Simula那样面向对象编程(OOP)的语言,于是他在C语言基础上创建了"C with Classes"。
  2. 1983年:语言正式更名为C++,名称中的"++"是C语言中的自增运算符,象征着在C语言的基础上的超越和进化,这一时期增加了很多的关键特性。
  3. 1985年:Bjarne出版了经典著作《The C++ Programing Language》第一版,同年,C++的第一个商业版发布。
  4. 1989年:C++ Release2.0发布,引入了多重继承和抽象类等特性。
  5. 1990年:《The Annotated C++ Reference Manual》(ARM)出版,成为事实上的标准,为后来的标准化工作奠定了基础,模板和异常处理等特性在此阶段加入。
  6. 1998年:第一个国际标准C++98发布,标志着C++正式成为一门成熟稳定的标准化语言。

第二阶段:经典C++时代(1998-2011)

这是C++普及和称为主流的黄金时期,但也暴露一些问题:

  1. C++98:第一个官方标准,它标准化了早在多年前就已经存在的特性:标准模板库(STL)等。
  2. C++03:于2003年发布的C++03,主要是一个修复版本修正了C++98标准中的许多缺陷和歧义没有引入新的特性。

这个时代的挑战:虽然强大,但C++也因为复杂,容易误用(特别是手动内存管理)、编译慢、以及某些特性(如元编程)晦涩难懂而受到批评,同时java等新语言的崛起给C++带来了巨大的压力。(所以啊,万事开头难)

第三阶段:现代C++时代(2011-2020)

  1. C++11:2011年发布的C++11时代圣伊莱最重要的一次更新,带来了大量的革命性的特征,几乎改写了人们编写C++的方式,像是在这时登场的:自动类推导、智能指针、范围for、初始化列表。
  2. C++14:2014年发布的C++14是C++11的增量更新,主要目标是完善和优化C++11引入的特性。
  3. C++17:2017年发布的C++17,带了许多实用的新特性和标准库组件:结构化绑定、类模板参数推导等。

第四阶段:当代C++新时代(2020年至今)

  1. C++20:C++20的重要性不亚于C++11:概念(彻底改变模板编程,让模板错误信息更清楚,接口约束更明确)、模块(取代传统的头文件,旨在解决编程速度慢和宏污染等难问题)、协程(为异步编程提供了原生支持,是编写异步代码的强大工具)等。
  2. C++:C++23是对C++20的补充和完善。

给大家分享一下官方文档:

C++ reference - cppreference.com

由于循环、数组等都是老生常谈的问题了,所以本文讲解将忽略这些相关内容,从C++基础开始讲解,感谢您的理解!

2.C++的第一个程序

标准流程还是要走一下滴,C++兼容C语言绝大多数的语法,所以C语言实现的方法还是可行的,当然C++有一套自己的输入输出格式,严格来说C++实现"hello world"是这样写的:

cpp 复制代码
#include<iostream>
using namespace std;
int main()
{
  cout<<"hello world"<<endl;
  return 0;
}

现在看不懂无妨,我们一会会讲到的。

3.命名空间

在我们书写C语言项目时,当多个库或者模块凑在一起,那些突如其来的重复定义、符号冲突等错误,就像一场没有规则的命名大战,然开发者头痛不已,C++中的命名空间(namespace)正是为了解决这种"命名污染"而生的强大工具。使用命名空间的目的是对标识符的名称本地化,以避免命名冲突,namespace关键字的出现就是针对这种问题。

在C语言中所有的全局变量、函数都共用一个作用域,当项目规模扩大,或引入多个第三方库时,就极易发生命名冲突,举个例子:假如你要编写一个计算随机数的函数rand(),同时引入了<stdlib.h>头文件:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
int rand=10;
int main()
{
  printf("%d\n",rand);
  return 0;
}

我们会遇到这样的报错,因为<stdlib.h>头文件中存在的rand()函数和你定义的全局变量rand名称相同,发生冲突编译器无法分辨,C语言常用的解决方案就是:**加个前缀(例如:my_rand)。**这样书写比较繁琐。

3.1命名空间的基本概念

C++引入命名空间的概念,允许开发者可以将代码逻辑的分组,形成独立的作用域,从而避免命名冲突。命名空间通过关键字namespace定义,其内部的标识符不会和外面的发生冲突:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
namespace test
{
  int rand=10;
}
int main()
{
  printf("%d\n",test::rand);
  return 0;
}

我们可以看到,当我们使用了命名空间就避免了命名冲突的问题。大家也看到了我在打印的时候写了test::rand,这是什么意思呢?这就是接下来要讲的访问命名空间内成员的方法:

1.作用域解析运算符(::)(最明确可靠)

这就是我刚刚在打印那里使用的访问方法,作用域解析运算符(::)的含义是:**编译器应从操作符左侧名字所示的作用域中寻找右侧那个名字。**其书写形式为:

cpp 复制代码
#include<iostream>
namespace test
{
  int a=10;//属于test这个命名空间的变量a
}
using namespace std;
int main()
{
   cout<<test::a<<endl;
   return 0;
}

2.使用using namespace引入整个命名空间

大家也发现了吧,在打印C++第一个程序和上面那种访问访问方法的书写格式中,我都使用了using namespace std这段代码,有了using声明的形式就无需专门的前缀(作用域解析运算符::)也可以直接使用所需要的名字了,using声明的形式如下:

cpp 复制代码
using namespace test;//直接引用test整个命名空间

3.使用using引入特定的成员(局部展开)

举个简单的例子:像是大家在网上观摩别的创作者的作品时,有的人写的打印是这样的:

cpp 复制代码
#include<iostream>
using test::rand;
int main()
{
  std::cout<<rand<<std::endl;//此时的rand就相当于test::rand
  return 0;
}

这种访问方法是命名空间的局部成员引入进来,不需要将整个命名空间展开。

总结:定义命名空间时,要使用namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中的就是命名空间的成员,可以是函数、变量等。namespace本质是定义了一个作用域,这个域独立于全局域,不同域的可以定域相同的变量名。

注意:位于头文件的代码一般来说不应该用using声明,这是因为头文件的内容会被拷贝到所有引用他的文件中去,如果头文件里面有某个using声明,那么每个使用了该头文件的文件都会有这个声明。对于某些程序来说,由于不经意的包含了一些名字,可能会产生始料未及的命名冲突。

3.2命名空间的使用案例

说了这么多,我们再来探探它的使用吧。namespace只能被定义在全局,当然还可以被嵌套定义:

cpp 复制代码
#include<iostream>
namespacr Class
{
  namespace studentA
   {
     
   }
}

在工作中或者在学校和同学合作一个项目时,通常会这样使用,每一个项目分给一个小组,小组中每个人再进行分配工作,难免会产生命名冲突,这样能更好地解决这个问题。注意:项目工程中相同名称的命名空间会被编译器自动合并,不会发生冲突。

4.C++的输入和输出

C++语言并未定义任何的输入输出(IO)语句,取而代之,包含了一个全面的标准库(standard library)来提供IO机制(还有很多其他的功能)。前面我们using namespace std中的std就是这个标准库的缩写。iostream标准库包含两个基础类型istream 和**ostream,**分别表示输入流和输出流。

标准库定义了4个IO对象,为了处理输入,我们使用了一个名为cin的istream类型的对象,这个对象也被称为标准输入,用于从控制台读取数据,它使用流提取运算符>>,>>会跳过空白字符(空格、制表符、换行符),且当输入类型不匹配时会导致流进入错误状态,我们看下面这个简单的例子来了解一下流输入:

cpp 复制代码
#include <iostream>
int main() {
    int age;
    std::cout << "Enter your age: ";
    std::cin >> age;
    std::cout << "Your age is: " << age << std::endl;
    return 0;
}

对于输出,我们使用一个名为cout的ostream类型的对象,这个对象也被称为流输出,用于向控制台输出数据。它使用流插入运算符<<,我们来看一下它的使用方法吧:

cpp 复制代码
#include <iostream>
int main() {
    int number = 42;
    std::cout << "The number is: " << number << std::endl; // endl 换行并刷新缓冲区
    return 0;
}

标准库还定义了其他两个ostream对象,名为cerr和clog。cerr主要用于输出错误信息。它不被缓冲,信息会立即显示,确保错误提示及时输出,大家看下面这个格式做个了解就可以:

cpp 复制代码
#include <iostream>
int main() {
    if (some_error_condition) {
        std::cerr << "An immediate error occurred!" << std::endl;
    }
    return 0;
}

clog用于输出日志信息。它是缓冲的,效率可能更高,适合记录日志,这个也是做个了解即可:

cpp 复制代码
#include <iostream>
int main() {
    std::clog << "This is a log message." << std::endl;
    return 0;
}
相关推荐
木心爱编程3 小时前
C++程序员速通C#:从Hello World到数据类型
c++·c#
※※冰馨※※3 小时前
【c#】 使用winform如何将一个船的图标(ship.png)添加到资源文件
开发语言·windows·c#
ulias2123 小时前
单元最短路问题
数据库·c++·算法·动态规划
崎岖Qiu3 小时前
leetcode380:RandomizedSet - O(1)时间插入删除和获取随机元素(数组+哈希表的巧妙结合)
java·数据结构·算法·leetcode·力扣·散列表
ZLRRLZ3 小时前
【数据结构】图
数据结构·算法·图论
Ada_疯丫头3 小时前
杰伊·温格罗教我数据结构与算法
算法
小白开始进步3 小时前
机器人集群调度算法简介与实现思路
算法·机器人
ajassi20003 小时前
开源 C++ QT Widget 开发(十六)程序发布
linux·c++·qt·开源
蜀中廖化3 小时前
bash:trtexec:command not found
开发语言·bash