C++初阶---C++基础入门(未写完)
Posted 4nc414g0n
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶---C++基础入门(未写完)相关的知识,希望对你有一定的参考价值。
C++基础入门
1)前言
1979年,贝尔实验室的本贾尼等人试图分析unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为
C with classes
。
C++98
:C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写C++标准库,引入了STL(标准模板库)
C++11
:增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等
C++14
:对C++11的扩展,主要是修复C++11中漏洞以及改进,比如:泛型的lambda表达式,auto的返回值类型推导,二进制字面常量等
除此之外还有C++1.0,2.0,03,05,17,20等,但都不是那么重要
2)C++关键字
C++共有63个关键字相对于C的32个相当于翻倍(
兼容C关键字
)
asm | do | if | return | try | continue |
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
3)命名空间(namespace)
许多相同的变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题
1.namespace的使用
下面是常规命名空间使用
namespace My_Space // My_Space为命名空间的名称 { // 命名空间中的内容,既可以定义变量,也可以定义函数 int a; int Add(int left, int right) { return left + right; } }
注意1
:命名空间可以嵌套namespace My_Space1 { int a; int b; int Add(int left, int right) { return left + right; } namespace My_Space2//嵌套 { int c; int d; int Sub(int left, int right) { return left - right; } } }
注意2
:同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中namespace My_Space // My_Space为命名空间的名称 { // 命名空间中的内容,既可以定义变量,也可以定义函数 int a; int Add(int left, int right) { return left + right; } } namespace My_Space // My_Space为命名空间的名称 { // 命名空间中的内容,既可以定义变量,也可以定义函数 int b; int Mul(int left, int right) { return left * right; } }
相当于
namespace My_Space // My_Space为命名空间的名称 { //上 int a; int Add(int left, int right) { return left + right; } //下 int b; int Mul(int left, int right) { return left * right; } }
2.命名空间的使用方式
1.加命名空间名称及作用域限定符’
::
'int main() { printf("%d\\n", My_Space::a); return 0; }
2.使用
using
将命名空间中成员引入using My_Space::b; int main() { printf("%d\\n", b); return 0; }
3.使用
using namespace 命名空间名称
引入using namespce My_Space; int main() { printf("%d\\n", My_Space::a);//可以这样 printf("%d\\n", b);//也可以直接使用 Add(10, 20); return 0; }
4)cout 与 cin
注意
:
使用cout标准输出(控制台)和cin标准输入(键盘)时必须包含< iostream >头文件以及std标准命名空间
关于C++中没有‘.h’的解释
:
早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中。后来将其实现在std命名空间下
,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h 旧编译器(vc 6.0)中还支持iostream.h格式,后续编译器已不支持,所以应使用<iostream>+std
的方式
基本使用方法(粗略了解)
cout
相当于printf
cin
相当于scanf
endl
相当于换行符‘\\n
’
cout可以自动识别变量类型(最多输出小数点后5
位)#include <iostream> using namespace std; int main() { int a; double b; char c; cin>>a; cin>>b>>c; cout<<a<<endl; cout<<b<<" "<<c<<endl; //不像printf,这里必须要用 输出流符号<< 来连接 return 0; }
5) 缺省参数
定义
:调用函数时可传参可不传,不传时使用函数自己指定的实参,如例子所示:void TestFunc(int a = 0) { cout<<a<<endl; } int main() { TestFunc(); // 没有传参时,使用参数的默认值 TestFunc(10); // 传参时,使用指定的实参 }
1.全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30);
2.半缺省参数
void TestFunc(int a, int b = 10, int c = 20);
注意
:
半缺省参数必须从右往左依次来给出,不能间隔着给
缺省参数不能在函数声明和定义中同时出现
(如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用哪个缺省值
)缺省值必须是常量或者全局变量
C语言不支持(编译器不支持
6)函数重载
定义
:函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表
(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
以下是函数重载
int Add(int left, int right) { return left+right; } double Add(double left, double right) { return left+right; } long Add(long left, long right) { return left+right; } int main() { Add(10, 20); Add(10.0, 20.0); Add(10L, 20L); return 0; }
以下不是函数重载
1.short Add(short left, short right) { return left+right; } int Add(short left, short right) { return left+right; }
2.
函数名修饰规则只是参数(个数 或 类型 或 顺序)不同和缺省参数没关系void TestFunc(int a = 10) { cout<<"void TestFunc(int)"<<endl; } void TestFunc(int a) { cout<<"void TestFunc(int)"<<endl; }
1. 为什么C++支持函数重载,而C语言不支持函数重载?
程序的运行,需要经历:
预处理、编译、汇编、链接
参考:C语言----程序编译(预处理)
在Linux下
:(假设a.cpp中调用了b.cpp中定义的func函数
)
链接阶段,链接器看到a.o
(二进制文件)调用func,但是没有func的地址,就会到b.o
(二进制文件)的符号表中找func的地址,然后链接到一起
在调用函数时一般会使用
汇编指令call
参见:函数栈帧的创建和销毁(详细)
形如
:80489bc: e8 73 ff ff ff call <?>
(问号也就是函数签名)
每个编译器都有自己的函数名修饰规则
gcc编译器
:
使用objdump工具,键入objdump -S 源文件
函数签名依然是func
g++编译器
:
同样的操作显示出的函数签名变为了<_Z4funcidPi>
而不是原函数名func
这里我们已经可以得出结论
:
在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中,构成函数签名
回到链接阶段
如果b.cpp中有两个func函数,由于在gcc编译器下会发现符号表中有两个重名的函数签名,所以会直接报错,因为不知道该拿出哪个地址。但g++就很明确了,两个相同函数名的函数签名不一样,所以可以轻松取出目标地址
这样一来,也可以说明上面不是函数重载的情况1(
与返回值无关
)
思考
:可以设计成返回值不同的重载吗?
答
:不能
- 如果同名函数仅仅是返回值类型不同,有时可以区分,有时却不能,在C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个Function 函数被调用。
- 把返回值带进修饰规则,编译器层面是可以区分的,但是在语法调用层面,很难区分,具有严重歧义(因为你只是调用函数名和参数)
参考:
C++的函数重载
在Windows下
:参考:C/C++调用约定
const/volatiles情况待补充
2. extern “C”
extern "C" int Add(int left, int right);
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern “C”来解决
总而言之功能主要用在下面的情况:
- C++代码调用C语言代码
- 在C++的头文件中使用
- 在多个人协同开发时,可能有的人比较擅长C语言,而有的人擅长C++,这样的情况下也会有用到
当C想用C++的接口(函数),C编译器直接用(
C++编译器用
exturn "C"
按照C规则编译出的
)二进制文件
3. 关于重载函数的调用匹配(待补充)
这里是引用
6)引用
定义
:引用是给已存在的变量取的一个别名,语法上程序不会引用变量开辟新内存,只是共用(一个变量可以拥有无数个别名)
void TestRef() { int a = 10; int& ra = a;//<====定义引用类型 printf("%p\\n", &a); printf("%p\\n", &ra); }
逐语句调试可以看到
ra和a的地址一样
,改变ra,a也会随之改变
特性
:
- 引用在定义时必须初始化
int a = 10;// int& ra; // 该条语句编译时会出错
- 一个变量可以有多个引用
如:int a=10;int& b=a; int&c =b; int& d=a
- 引用一旦引用一个实体,再不能引用其他实体
如:int a=10;int& b=a; int c=20; // b=c; //该条语句编译时会出错
例子
:int x=0,y=1; int*p1=&x; int*p2=&y; int*& p3=p1; *p3=10; p3=p2;
分析
:
p3已经是p1的别名了不能做其他的别名了所以改的是p1的指向
常引用
例子
:void TestConstRef() { const int a = 10; //int& ra = a; // 不可以 }
void TestConstRef() { int a = 10; const int& ra = a; // 可以 }
void TestConstRef() { double a = 14.56; int& ra = a; // 不可以,类型不同 }
注意
:
变成别名的条件:可以不变或缩小
原变量的读写权限,但不可以放大
读写权限
关联
:C语言----数据的存储(以前的博客写的不好,这里补充
)
C++语言编译系统提供的内部数据类型的隐式自动转换规则
如下:
- 执行算术运算时,低类型(短字节)可以转换为高类型(长字节);例如: int型转换成double型,char型转换成int型等等;
- 赋值表达式中,等号右边表达式的值的类型自动隐式地转换为左边变量的类型,并赋值给它;
- 函数调用时,将实参的值传递给形参,系统首先会自动隐式地把实参的值的类型转换为形参的类型,然后再赋值给形参;
- 函数有返回值时,系统首先会自动隐式地将返回表达式的值的类型转换为函数的返回类型,然后再赋值给调用函数返回。
在C/C++中
显式(强制类型)转换
和隐式类型转换
会产生临时变量
例
:void TestConstRef() { int i = 10; double d = i; const double& r = i; }
对于
double d = i;
:相当于i
->(临时变量(double)
)->d
对于const double& r = i;
:i
->(临时变量(double)
)->d
(double& r
只是临时变量的别名,而临时变量具有常性
(不是常量
),所以要加上const
)
C++ 临时变量的常量性 这篇博客说到:(临时变量并不是常量,只是编译器从语义层面限制了临时变量传递给非const引用,意在限制非常规用法的潜在错误)
引用作为参数
以栈的接口StackInit和PrintStack为例:
StackInit(ST& s); PrintStack(const ST& s);
传引用
:
- 为了新参改变实参(代替C的指针)
- 传引用减少拷贝(和传指针一样,传值会开辟同样大的一块空间)
- PrintStack这种只读函数中传const引用可以防止函数逻辑中出现"==“写为”="的情况
传引用的效率(关联传址和传值)
这里是引用
引用作为返回值
一般情况
:是传值返回int Add(int a, int b) { int c=a+b; return c; }
所谓传值返回
:(函数销毁了函数里的c也被销毁了,只有通过临时变量)
- 当return的值占空间较大的时候,会在main函数中开辟一块临时空间
- 当return的值占空间较小时,通常是用eax寄存器传递
当引用作为返回值
:
引用和指针区别
以上是关于C++初阶---C++基础入门(未写完)的主要内容,如果未能解决你的问题,请参考以下文章