从C到C++——C++基本编程体验

一、何为命名空间

1、命名空间的引入
(1)命名空间namespace,是C++引入的一种解决全局变量和函数名(其是全局的)冲突的机制

(2)C语言没有namespace,但是C++及之后的java python等都有

(3)namespace的关键点有2个:一是如何解决名称冲突,二是如何合法访问变量

2、C语言如何解决名称冲突
(1)大项目中会有很多C文件,全局变量和函数都是extern链接属性,因此名称冲突是客观存在

(2)一个项目是一个单体程序,项目中的全局变量和函数理应能互相访问,因此名称冲突是客观存在

(3)C中解决名称冲突的办法是:
第一,同一个C文件不要太大,由一个人写;
第二,每个C文件(或几个C文件构成的一个模块)中所有全局变量和函数前加统一的唯一前缀;
第三,不需要文件外访问的全局变量和函数前面都加static

(4)C语言的解决方案可行,C++早期就是这样做的,但这种方法太low

3、命名空间如何解决问题
(1)为实现命名空间机制,C++引入了namespace关键字,定义格式为namespace xx{};

(2)一个特定名称的namespace的一对大括号内部定义的变量、函数、类等均属于该命名空间内

(3)在命名空间内部互相引用时可以直接使用变量名、函数名等

(4)跨命名空间互相引用时必须同时指定被引用方的命名空间名和变量名函数名才可以找到

(5)命名空间看起来就好像一种前缀

(6)命名空间本质上其实是对全局变量和函数在一定范围内链接属性的更改和控制(在命名空间之内的是extern,在命名空间之外是static的)

4、关于语言特性的思考
(1)语言特性是语言通过关键字或符号所支持的一种功能特性,如namespace、template、运算符重载、面向对象等。

(2)语言特性必定对应解决某种问题,必定在某方面对程序员有帮助

(3)语言特性越多或者设计越复杂,则语言本身就越难使用,但语言就越厉害

(4)语言特性体现为某种语法,本质上靠编译工具链提供支持

(5)C++11/14/17/20的版本变迁,无非是新增或修正某些细节语言特性

(6)就事论事讨论编程语言,其实难点都在掌握和熟练运用语言特性上

二、namespace的初级定义和使用

1、同一文件内定义namespace
正常情况,一个文件使用一个namespace,或者几个文件共同使用一个namespace

2、同一文件内使用namespace
::(作用域解析符)
using关键字:最常用的作用是导入命名空间

(1)namespace的三种引用方法
// 方式一
ace::Mutex mutex; 成本最低,用到那个,就去现场引用那个,没有额外的负担对编译器

// 方式二
using ace::Mutex; //一次性只声明了指定命名空间里面的一个变量或函数,成本较低

using NS1::func1//告诉编译器如果在默认的命名空间找不到func1可以到NS1中去找一下

Mutex mutex;
// 方式三
using namespace ace; //一次性声明指定命名空间里面的所有变量函数等
成本更高,但更方便
Mutex mutex;

第一种是用到哪个就去声明哪个,没有引入额外的负担,什么意思呢?using这里有点像include关键字,就是把整个文件拿过来在这里原地展开,假设include的包含的那个文件有700行,include这行代码就相当于把那700行的代码全部搂过来了,这种做法还是有一定开销的,对编译器的压力就比较大。

第二种只using进来一个符号,第三种把命名空间的所有符号全部都using进来了,从成本的角度来说,第一种到第二种到第三种的成本是依次递升,但是方便性是越来越好的。实战当中第一种和第三种用的比较多,第二种用的比较少。

2、不同C文件间定义和使用namespace

namespace NS2{void func3(void);}; //你有东西在另一个文件中,你要使用可以,但是你得先声明

namespace NS2{extern void func3(void);};
//namespace这种思想与要使用跨文件使用变量或函数是类似的,声明的时候不可以有实体,有实体就变成定义了

using namespace NS2; //命名空间引用

三、C++默认命名空间

1、先解决上节遗留问题
(1)如何声明一个namespace?

(2)注意体会这种学习方法,核心是:第一步明确问题,第二步自我分析,第三步实验验证,总纲是形成自我知识体系,本质上站在和语言特性设计者一样的高度来看待和学习研究语言。

2、默认命名空间(只有一个,main函数必须放在其中)

在C++语言,所有的代码都应处于某个命名空间中,在同一个namespace中直接引用(类似于C语言中的一个文件内的函数引用),在不同的namespace中,通过上面的方法进行引用(同一个文件内)、跨文件引用。

所有的没有明确指定放在哪一个namespace中,就是在默认的namespace。

(1)又叫全局命名空间
(2)默认命名空间引用其他命名空间方法(上节最后的内容:using namespace NS2; )
(3)默认命名空间引用自己的方法
(4)其他命名空间引用默认命名空间中的方法::f();

void func5(void);      //函数的声明要放在引用它的命名空间之前,因为编译只会往前回顾,不会往后看的。  
namespace NS1
{
    void func2(void)
    {
        ::func5();	
/*	表示func5定义和声明在默认命名空间,因为命名空间的名字是空的,所以冒号的前面是空的,也可以省略双冒号,最好加上,不加的话给人的感觉func5()是NS1里面定义的函数*/
       //func1();   //如果是特定的命名空间内部访问自己的东西,前面什么都不用加
    }
	
    void func1(void)
    {
        
    }
	
};

在一个命名空间中调用不加前缀的函数(来自其他namespace,但未声明),默认会在自己的这个命名空间中进行寻找。

四、C++匿名命名空间(没有名字的namespace)

1、std和cout的引入
(1)std是C++标准库定义好的一个namespace
(2)cout是std这个namespace下的一个标准输出工具,类似于C中的printf
(3)用法示例

2、匿名命名空间的定义和使用
(1)定义
匿名命名空间和全局命名空间有些类似,他们都没有名字,但是全局命名空间不需要自己定义,而匿名命名空间需要自己去定义,方法就是namespace后面没有空间名,直接就是 {}
namespace
{
void func(void)
{
std::cout<<“来自匿名命名空间的func().”<<std::endl;
}
}

(2)匿名命名空间中引用其他命名空间中的方法(与上小节讲的方法一样)

(3)匿名命名空间中引用自己命名空间中的方法(确保先定义后使用)
直接用,无需添加命名空间名前缀,只要确保先声明后使用的顺序就没有问题。

(4)其他命名空间中引用匿名命名空间中的方法

一般匿名命名空间定义于文件前面,类似于全局函数位置,之后的命名空间可以正常访问匿名命名空间中的内容,但是匿名命名空间的作用不在于此,而是在于跨文件访问的时候。因此其实对于单个文件内部,使用匿名命名空间与全局函数并无多大差别。

3、匿名命名空间的价值
(1)匿名命名空间中的符号纯(只在)文件内部使用,不需要被外部引用

(2)匿名命名空间效果类似于全局变量和函数加static,但是比C中的static使用范围广,C中static只能使用到函数和全局变量上,无法用到结构体以及枚举类型等等,但匿名的namespace可以。

在文件内部没有什么作用好像没有一样的,其内的东西是全局的,但改变了文件的外部链接属性。

(3)匿名命名空间的用法逻辑上符合整个命名空间的一贯原则

示例代码:

#include <iostream>

using namespace std;

namespace 
{
	extern void func2(void);
}

namespace NS
{
	void func1(void)
	{
		cout << "func1 from namespace NS" << endl;
		func2();
	}
};

int main(void)
{
	NS::func1();
	return 0;
}

五、嵌套命名空间

1、嵌套namespace的定义和使用
(1)定义
(2)外部引用嵌套命名空间内的符号
(3)嵌套命名空间内部不同层次间符号的互相引用

#include <iostream>

using namespace std;

namespace NS1
{

	namespace NS2
	{
		void func1(void)
		{
			cout << "NS1::NS2::func1." << endl;
		}
	}
	
	void func1(void)
	{
		cout << "NS1::func1." << endl;
	}
	
	void func2(void)
	{
		cout << "NS1::func2" << endl;
		NS2::func1();
		func1();
	}
			
}

using namespace NS1::NS2;

int main(void)
{
	NS1::NS2::func1();
	func1();
	NS1::func2();
	
	return 0;
}

2、namespace的总结
(1)记住最终目标:解决全局名称冲突,同时提供合法互相访问的机制

(2)直接用法(语法),记住如何定义、如何声明、三种使用方法

(3)特殊情况有默认/全局命名空间和匿名命名空间,均为符合一贯原则的特例用法

(4)再次体会"语言特性"的含义

语言特性:如何支持一种语言特性(如namespace)——通过语法,通过编译器、链接器等实现,带给我们解决某种问题的办法

(5)C++的语言特性多而复杂,因此学习和使用难度高,关键在于掌握方法,从本质上学

/***************************************************************************************
补充内容
\r与\n的区别:
\r\n的区别以及对应的ascii码

\r:回车,ASCII码13        r->return

\n:换行,ASCII码10     n->newline

在Windows中:

'\r'  回车,回到当前行的行首,而不会换到下一行;

'\n'  换行,换到当前位置的下一行,而不会回到行首;

、r与\n的区别:

\r : return 到当前行的最左边。

\n: newline 向下移动一行,并不移动左右。

Linux中\n表示回车+换行;

Windows中\r\n表示回车+换行。

Mac中\r表示回车+换行。

************************************************************************************/

六、C++标准库介绍

1、C++是C的超集
(1)一个典型C程序(后缀名.c)可以完全被视为C++程序来编译,可使用g++来编译;

(2)C程序可以通过__cplusplus符号是否预定义来判断当前是gcc还是g++编译 ,C++编译器中才有__cplusplus。
__cplusplus的值是long int类型的,值表示当前编译程序的C++编译器的版本号。

(3)一个典型C++程序(后缀名.cpp)只能当C++程序来编译,可见C++是C的超集

(4)C++文件名的常用后缀:源文件(.cpp .cxx .cc .c .c++),头文件(.hpp .hxx .h)

printf其实是属于std命名空间的,之所以未导入命名空间就可以用,是因为C++为了兼容C语言,但并不建议这么用,建议这样包含再使用该语句

2、C++完全接收并兼容了C库
(1)典型C++程序中可以支持C的形式包含C库头文件,并直接使用C库API

(2)C++更建议的头文件包含形式不是<stdio.h>这样,而是(内部还是包含了:#include <stdio.h>)这样

C语言中的xx.h头文件对应了C++中的cxx,其中包含了#include <xx.h>
并有一些命名空间的定义及声明。

要点:C++的标准库的头文件是没有后缀名的

(3)ubuntu中gcc工具链的头文件在/usr/include中,可以实际看看

3、C++标准库介绍
(1)C标准库即为C++标准库的一部分,完全继承并以C++方式重写,位于std命名空间中

(2)C++面向对象库,如string、iostream等,位于std命名空间中

(3)C++ STL标准模板库,如vector、map等,位于std命名空间中

4、C++标准库的地位和学习安排
(1)C++比C在实际工作中更依赖于库,所以学好C++标准库很重要

(2)C++标准库蕴含了C++的各种语言特性的典型用法,学标准库就顺便学好了C++

(3)继承C标准库的部分兼容C的玩法,不用讲了

(4)C++面向对象库难度不大,本次先讲iostream和string等初步用法

(5)STL部分是重难点

summary:越高级的语言库占得比例越多,语言占得越少,比如python,学好其要学好它的库,学标准库就是在学C++;

七、iostream的cout使用

C语言中的 stdout 是一个定义在<stdio.h>的宏(macro),它展开到一个
FILE* (“指向 FILE的指针”)类型的表达式(不一定是常量),这个表达式指向一个与标准输出流(standard output stream)相关连的 FILE 对象

1、基本使用
<< 流操作符,做输出, >> 做输入

(1)cout即标准输出,对应stdout

(2)cout定义在std命名空间中,要按三种使用方法来用

(3)结合<<符号进行输出,可多节连接

#include<iostream>
using namespace std;
int main(){
    int x;
    float y;
    cout<<"Please input an int number:"<<endl;
    cin>>x;
    cout<<"The int number is x= "<<x<<endl;
    cout<<"Please input a float number:"<<endl;
    cin>>y;
    cout<<"The float number is y= "<<y<<endl;   
    return 0;
}
运行结果如下(↙表示按下回车键):
Please input an int number:
8↙
The int number is x= 8
Please input a float number:
7.4↙
The float number is y= 7.4

(4)cout涉及的头文件有

:输入输出的格式化相关的内容
<bits/ios_base.h>
一般常使用前两个,因为第三个在第二个中包含了

(5)cout本质上是ostream(iostream的派生类)的一 个对象

(6)流操作符<<本质上是左移运算符在iostream中的运算符重载

2、流操作符的格式化细节
cout、endl是iostream中的一个对象
(1)网络搜索C++ cout使用,大把文档详细讲
http://c.biancheng.net/view/275.html

(2)cout现在主要用来输出调试信息,掌握主要用法和细节查询即可,不必去记。

#include<iostream>
using namespace std;
int main(){
    int x;
    float y;
    cout<<"Please input an int number and a float number:"<<endl;
    cin>>x>>y;
    cout<<"The int number is x= "<<x<<endl;
    cout<<"The float number is y= "<<y<<endl;   
    return 0;
}
运行结果:
Please input an int number and a float number:
8 7.4↙
The int number is x= 8
The float number is y= 7.4   

八、iostream的cin使用

1、基本使用、注意点
(1)输入的时候不要使用引用符& scanf("%d", &val);

(2)cin的输入会以空格为中断,若输入数字没什么问题,用空格分割输入的内容,但若输入字符串,空格后的内容就不会输入了,其会认为这是两个字符串,虽然你认为是一个字符串,

例如:hello world!,不能输入这样一个字符串,可两次输入实现

九、C++用fstream读写文件

1、fstream介绍
(1)fstream是C++标准库中面向对象库的一个,用于操作流式文件

(2)fstream本质上是一个class,提供file操作的一众方法

详解:https://blog.csdn.net/xuleisdjn/article/details/79040688

2、C++标准库查询参考
(1)man手册:https://blog.csdn.net/u012675539/article/details/50257343

https://blog.csdn.net/weixin_34352449/article/details/94710556?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7.control&dist_request_id=1328680.10915.16161546458686963&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-7.control

(2)http://www.cplusplus.com/reference/

(3)https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5

3、fstream使用举例
(1)打开/创建文件,并写入内容,保存关闭

(2)打开文件,并读取内容显示,最后关闭。

#include <fstream>
#include <iostream>
#include <iomanip>

using namespace std;


int main(void)
{
	fstream fs;		// 定义了一个fs对象用来做后续操作
	char str[] = "i love c++";
	char rstr[16] = {0};
	
	fs.open("1.txt");
	if (fs.is_open())
	{
		cout << "open success" << endl;
	}
	else
	{
		cout << "open fail" << endl;
	}
	
	// 文件写入
	fs.write(str, sizeof(str));
	fs << str << endl;
	
	// 文件读出
	fs.read(rstr, sizeof(rstr));
	fs >> rstr;
	
	cout << "read content: " << rstr << endl;
	
	// 文件关闭
	fs.close();
		
	return 0;
}

十、C++字符串string类使用

1、C++式字符串的使用
(1)参考手册文档
(2)代码实践

在使用string类时,需要包含头文件 #include,并且引入using std::string; using std::wstring;或using namespace std;下面你就可以使用string/wstring了,它们两分别对应着char和wchar_t。string和wstring的用法是一样的

2、C++字符串和C字符串的对比
(1)C语言严格说没有字符串的概念,C字符串其实就是字符数组或字符指针
(2)C++和之后的java等都有字符串,本质是一个class
(3)C++字符串的优势是标准库自带可用于字符串的各种处理算法和方法
(4)C++实际开发中建议使用C++字符串而不是沿用C式字符串

十一、C与C++混合编程
1、为什么需要混合编程
(1)C有很多优秀成熟项目和库,丢了可惜,重写没必要,C++程序里要调用
(2)庞大项目划分后一部分适合用C,一部分适合用C++
(3)其他情况,如项目组一部分人习惯用C,一部分习惯用C++

2、为什么不同语言可以混合编程
(1)程序编译过程:源文件->目标(库)文件->可执行程序->镜像文件
(2)任何编程语言执行时都必须是可执行程序,所以都必须先被编译成目标文件
(3)混合编程的“混合”操作发生在链接这一步

3、C++和C混合编程的困难所在
(1)C++和C都是编译型语言,互相混合相对容易

(2)难点:C++支持函数名重载,而C不支持,因此编译器生成目标文件时,函数名在目标文件中的临时内部名称规则不同。导致链接时符号对不上

eg:
C语言函数:
int add_int(int a, int b);
int add_char(char a, char b);

编译后生成的中间名字:
add_int
add_char

C++函数名重载机制:
int add(int a, int , b);
int add(char a, char, b);
int add(int a, flaot b);

编译后生成的中间名字:
addii
addcc
addif 

(3)解决方案:使用extern “C”{}; 让C++在对接的局部向C妥协兼容

3、使用objdump工具来研究函数编译后的符号
(1)写个典型的C语言库mylib.c和mylib.h,提供add和sub等几个函数

(2)使用gcc -c xxx.c -o xxx.o编译得到(静态)库文件,再objdump -d反汇编得到.i文件
gcc -c只编译不连接,生成.o文件

(3)对比加不加extern "C"这2种情况下得到的.i文件的符号差异

实验第1步:证明了C语言中名称为add的函数,编译后符号表中就叫add
gcc xxx -c -o xxx

实验第2步:证明了C++语言中名称为add的函数,编译后符号表中叫_Z3addii
g++ xxx -c -o xxx

g++ xxx.cpp -lxx -L.

objdump -d xxx.o > xxx.i
分析:同样的源码,编译后生成的二进制代码其实是一样的,所以功能其实也是一样的。所以本质上是可以混合编程的,但是生成的中间符号名称不同,所以链接器难受

实验第3步:证明了在C++的头文件中,只要把C++的函数的声明放在extern “C”{}的大括号范围之内,就可以让g++在编译这个函数时生成中间符号名时按照C的规则而不是按照C++的规则,所以这样的函数就可以和C的库进行共同链接。

4、C与C++混合编程的可能情况分析
(1)同一个项目C/C++全部有源码,一次编译链接。
(2)同一个项目中C是库,C++是源码,C++调用C
(3)同一个项目中C++是库,C是源码,C调用C++

5、第一种情况
(1)可能性1:全部使用g++编译。不推荐
(2)可能性2:在C的头文件中加extern "C"声明

6、第二种情况
(1)这种是最典型的常见情况

(2)通用解决方案:在C的头文件中加extern "C"声明,在C++中直接包含头文件调用即可

7、C调用C++的麻烦
(1)g++和gcc的编译时符号差异

(2)c++支持很多c并不支持的特性,如函数重载

(3)解决方案:添加一层封装层,因为此时已形成 c++的库,已按照C++的标准编译了,所以无法修改源码添加extern “C”{},所以增加一个封装层,在封装层是上使用extern “C”,封装层使用C++

8、代码实战:C调用C++库中的函数
(1)用cpp写一个库,mylib.cpp mylib.hpp,用g++编译成静态库(根据我的操作使用GCC编译结果好像与G++一样)

(2)objdump反编译库,查看确认符号

(3)用cpp写一个封装层,用上extern “C”(因为其会被 c调用,要兼容C的规则),cmylib.cpp和cmylib.hpp,用g++编译成静态库

(4)objdump反编译库,查看确认符号

(5)用c写一个main.c,调用wrapper库,用gcc编译链接,运行查看结果封装了两次,依赖于两个库,编译的时候两个库都要指定:
g++ xxx.c -lxx -lxx -L.

代码文件链接:https://www.aliyundrive.com/s/PfUxg4vA4ms

注:本资料大部分由朱老师物联网大讲堂课程笔记整理而来,如有侵权,联系删除!水平有限,如有错误,欢迎各位在评论区交流。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小嵌同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值