由C过渡到C++-入门知识点
Posted Booksort
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由C过渡到C++-入门知识点相关的知识,希望对你有一定的参考价值。
从C语言过渡到C++,这些知识点应该是比较重要的。
目录
第一个C++程序
#include <iostream>
void print(void)
{
std::cout << "hello world" << std::endl;
}
int main(void)
{
using namespace std;
cout << "hello world" << endl;
print();
return 0;
}
对于C语言而言,直接printf("hello world");
但是对于C++而言,需要一个using namespace std;
。
这个叫使用命名空间(名称空间)。
名称空间特性
名称空间支持是C++的一个特性,方便在大型项目由多个部门共同编写时,解决多个变量名相同的情况。对于不同部门,封装不同的域作为名称空间。可以解决命名冲突的问题
同一项目中,如果在不同文件中有同一个名字的命名空间,在最后会讲这一样的命名空间合并成一个命名空间。
像这种using namespace std;
是其std名称空间中的所有组件都能使用,当然仅限域其作用域中。
而std::cout
是只能使用限定的组件。
还有一种使用方法
#include <iostream>
using std::cout;
using std::endl;
int main(void)
{
cout << "hello world" << endl;
return 0;
}
提前在全局域声明。后面函数就能使用了。
而::
符号为:域作用限定符
在C语言中也存在,但我见的比较少。
#include <stdio.h>
int a = 10;
int main(void)
{
int a = 20;
printf("%d\\n", a);
printf("%d", ::a);//这样可以调用全局变量
return 0;
}
对于局部变量和全局变量有一模一样的变量,在编译时,会先查找局部域,如果没找到才去查找全局域。
而::a
可以这样调用全局域的变量,因为,空白也就是相当于全局域。
对于C++也同样理解,去std这个名称空间的域中查找使用其中的函数或其他组件。
cout
COUT<<"HELLO WORLD";
<<
将字符串发送给cout
从C++概念上看,输出是一个流,这个操作就是将字符串插入到流中。
cin
C++的输入形式cin>>num;
理解成,cin从输入流中读取信息放入变量中。
<<
和>>
都是表示信息的流向。
缺省参数
是C++支持的一种特性。从某种角度来看,是备胎,是备用数据。
概念:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,
如果没有指定实参则采用该默认值,
否则使用指定的实参。
就是在调用函数的时候,如果没有传实参,那么函数就会使用在定义或声明的时候提前指定的参数,防止函数出错。
全缺省参数
#include <iostream>
int add(int a , int b = 200,int c=10)
{
return a + b + c;
}
int main(void)
{
std::cout << add()<<std::endl;
std::cout<<add(100,100,500);
return 0;
}
输出结果:>
半缺省参数
#include <iostream>
int add(int a , int b = 0,int c=0)
{
return a + b + c;
}
int main(void)
{
std::cout << add(1)<<std::endl;
std::cout<<add(100,100,500);
return 0;
}
但是,如果有参数未指定,那么就一定要传递实参。
同时,对于半缺省参数,
1,未指定的参数一定要从左向右排列,指定的参数从右向左排列,
不能中间既有指定参数又有未指定参数,不能跳跃分布。
2,对于缺省参数,不能在函数声明和定义中同时出现,万一给的参数不一样,会导致编译器出错。
3,缺省参数必须是全局变量或者常量
重载
概念:
是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的
形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
也就是在不同的环境下,其函数因为已经提前定义,一样的函数名但可以运行且满足需求。
看例子
#include <iostream>
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
double add(double a, double b,int c)
{
return a + b+c;
}
int main(void)
{
std::cout << add(1, 3)<<std::endl;
std::cout << add(1.2, 2.5)<<std::endl;
std::cout<<add(1.2, 1.3, 6);
return 0;
}
输出结果
对于函数重载的要求:
形参列表(参数个数 或 类型 或 顺序)必须不同,只有返回类型不同不是重载
提问:为什么C语言不支持重载而C++支持
这一切都与C++的函数命名修饰规则有关
我们都知道无论是C/C++运行起来都需要经历这几个阶段
参考C语言编译过程
- 预处理->>头文件展开,宏替换,删除注释,条件编译
- 编译->>检查语法问题,语义分析,词法分析,符号汇总(函数,变量)
- 汇编->>将汇编翻译成机械码,各个文件都要汇总成符号表
- 链接->>所有文件中的符号表合并,链接在一起生成exe文件
大致过程
C++程序大致也是这个过程。
但是,C++针对函数,与C语言有着明显的不同。
C++要支持重载这个性质,那么对于函数就有自己的函数命名修饰规则
C/C++在编译的时候,将所有的符号汇总,在汇编的时候,要翻译成汇编语言,同时形成符号表。也就是对每个函数都会根据声明形成同一个符号表。
汇编:转成机械码后,
对每行函数调用都会转变成一行机械指令。
add(1, 3);//call _Z3addii(地址)
add(1.2, 2.5);//call _Z3adddd(地址)
add(1.2, 1.3, 6);//call _Z3addddi(地址)
当然,如果是分文件写的,函数声明和定义分开在不同文件。
那就找不到函数的地址,因为,各个文件分别形成符号表,在链接前是不会相互联系。只有在链接时,声明才会去其他文件找定义,才能知道函数的地址。
如果定义声明都在一个文件中,直接就知道函数地址了,不用等到链接时。
再来看看C语言的
add(1, 3);//call <add>
指令直接函数名。
根本不存在重载的情况。
C++的机械码指令还会附带显示参数类型,而C语言只是显示函数名。
C++的函数名修饰规则直接提供了重载的可能。
对于重载的判断
1,在查找符号表的同时,会判断是否有相同函数名的调用。
2,如果有,就会去匹配与传参类型数目匹配的函数
3,如果有匹配就执行,无匹配就报错。
如果出现二义性也会报错
总结:
C++会根据参数列表去与函数参数列表匹配,去执行响应的函数。
引用
概念:引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它
引用的变量共用同一块内存空间。
这也是C++的特性之一,与之相对应的是C语言中的指针。
引用:就相当于取了个别名int& b=a;
标识符a代表那个空间的所储存的值,而标识符b也代表a空间存储的值。b就是a,不是形参。
但指针容易出错,而引用不容易出错,且,更好理解。
这是C++实现的
#include <iostream>
void swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main(void)
{
int a = 10, b = 30;
std::cout << a << " " << b<<std::endl;
swap(a, b);
std::cout << a << " " << b;
}
这是C语言的
#include <stdio.h>
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void)
{
int a = 10, b = 30;
printf("%d %d", a, b);
swap(&a, &b);
printf("%d %d", a, b);
return 0;
}
再来看看这个
#include <iostream>
int main(void)
{
int a = 10;
int& b = a;
int& c = b;
c = 100;
return 0;
}
对代码进行调试
1,
2,
3,
4,
由此可见,当c改变时,a,与,b都一起改变了。
这只能说明一件事
a/b/c这2个标识符都代表这个空间。
都可以通过这任意一个标识符访问到该空间。只是有不同的名字罢了。
引用:就是为该对象取了另外一个名字,无论是什么名字,真实作用的还是该对象。
注意
- 引用在定义时必须初始化(不然无意义)
- 一个变量可以有多个引用
- 引用一旦引用了一个实体,就不能再引用另一个实体,否则不能运行。
#include <iostream>
int main(void)
{
int a = 1;
int m = 10;
int& b = a;
int& c = b;
int& d = m;
c = d;
return 0;
}
b/c都是a的别名
d是m的别名
而
c=d;
并不是让c是d的别名,而是单纯的操作
将d的值赋给c。
也就是将m的值赋给a.
引用一旦定义后,就不会轻易改变,比指针稳定,安全。
常引用
例->>
#include <iostream>
int main(void)
{
int a = 10;//a可读可写
const int& a1 = a;//a1操作只能都不能写//对权限缩小
//a1 = 12;//->对与a1的操作权限放大
a = 12;
const int b = 10;//b的操作只能都不能写
const int& b2 = b;//b2只能读不能写
//int& b3 = b2;
//int& b1 = b;//->对b的权限放大
}
有const,对与引用而言
权限可以缩小,不能放大
int a=10;
double d = 1.11;
//d = a;//隐式类型转换
//a = d;
double& tmp = a;
const double& tmp1 = a;
对于a=d;d=a;
这种叫隐式类型转换。
这并不是直接的作用,而是创建了一个要转换类型的临时变量,在赋值。
引用与函数返回值
#include <iostream>
int add(int a, int b)
{
return a + b;
}
int main(void)
{
int ret = add(10, 20);
return 0;
}
这个int ret = add(10, 20);
实际上,其ret接收的是一个临时空间里的值。
如果,使用引用。
#include <iostream>
int& add(int a, int b)
{
int c = a + b;
return c;
}
int main(void)
{
int ret = add(10, 20);
return 0;
}
返回的就是对这个临时空间的引用。
而c变量一旦函数结束,该空间就销毁了,没有了访问权限。
所以只能开辟一个临时空间,用于返回值。
而引用也就是该空间的引用。
但是对于随时会销毁的空间而言,引用并不适合用于返回值。
为什么?
来看看这个
#include <iostream>
int& add(int a, int b)
{
int c = a + b;
return c;
}
int main(void)
{
int& ret = add(10, 20);
add(20, 50);
std::cout<<ret;
return 0;
}
这个ret打印是70
有点意想不到。
因为,当一个函数结束后,那个空间就会被销毁。而返回的引用却还是引用那块空间。当下一个函数调用时,又会建立栈帧,在main函数
下的那块空间。
也就是上一个函数建立栈帧的空间。而返回的临时空间还是原来的地址,那引用的空间不会变,但是其临时空间中的值被改变了。
当std::cout<<ret;
时,是打印ret
所引用的空间的值。虽然越界了,但是编译器却没有检查出来。
对于引用而言,如果所引用的那块空间不稳定,容易被销毁后出现创建。那所引用的值也是不确定的。
对于此返回值并不推荐用引用。
如果是
#include <iostream>
int& add(int a, int b)
{
static int c = a + b;
return c;
}
int main(void)
{
int& ret = add(10, 20);
std::cout << ret<<std::endl;
add(20, 50);
std::cout << ret;
return 0;
}
因为静态变量存放在静态区,是不会轻易被销毁的,可以使用引用。
对于指针和引用的区别
引用的底层实现
在汇编实现的时候,引用和指针实现的底层都是一样的,引用是靠指针的底层实现,只不过编译器会对其进行打包。
都是依靠传地址进行操作。
两者在语法上
引用:
只是为变量空间取了一个别名,并未开辟新的空间,增加了一个新符号,还是原来的空间。
指针:
指针开辟了4个字节的空间,存储目标空间的首字节的地址。
两者在物理上
两者底层都是一样的,都是使用指针进行操作。
对于两者在效率上,其实都差不多,因为,底层都是靠指针进行操作,只不过引用被打包了一下。
两者的不同之处
- 引用在概念(语法)上是定义一个变量的别名,而指针是存储一个变量的地址。
- 引用在定义时必须要初始化,而指针没有要求
- 引用在初始化引用一个实体后不能再引用其他实体(变量),而指针可以在任何时刻都可以随便改变指向同类型的实体。
- 没有NULL引用,但有空指针。
- sizeof():对于引用实际上是计算引用类型的字节大小,而对于指针,却是地址所占空间的字节大小。
- 引用自增,就是其引用的实体加1,而指针自增则是其指向的地址向后移动一个类型大小。
- 有多级指针,但没有多级引用,有多次引用。
- 引用不需要解引用,编译器已经处理好了,而指针需要显示解引用。
- 引用比指针更安全。
内联函数
在C语言中,为了减少小一点的,多次调用的函数的开销,会使用宏函数来减少时间消耗。
例如:
#include <stdio.h>
#define Add(x,y) ((x)+(y))
int main(void)
{
printf("%d",Add(1, 2)*4);
return 0;
}
直接替换,还减少了调用函数,为函数开辟栈帧,跳转到函数位置的时间。
但是C语言的宏函数也存在一些不足:
- 不支持调试
- 没有安全检查
- 语法复杂,容易出错。
而C++提供了另一种方法来解决C语言的一些不足,也就是内联函数
效果与宏差不多。
这张图很清晰的描述了内联函数的过程。
要使用内联函数的特性,必须要
在函数声明前加上关键字
inline
在函数定义前加上关键字inline
但是,当使用内联函数时,编译器并不一定会执行。当编译器认为函数过大或者函数使用了递归,就不会处理为内联函数。
注意,分文件函数的声明和定义时,是不可行的,因为内联函数就是直接插入main文件中,
是不会建立栈帧的,即函数是不会有地址,不会形成符号表。所以不要将内联函数的声明和定义分文件。
对下面代码进行汇编查看
#include <iostream>
inline int Add(int a, int b)
{
return a + b;
}
int main(void)
{
int c = Add(1, 2);
}
如图
这就是形成汇编代码时,内联函数和常规函数的区别。
内联函数在汇编时,没有call指令
是不会调用Add函数的。
而常规函数是由call指令
,会去一个地方调用函数。
这就是内联函数对于常规函数的优势。时间更快
使用内联函数可以减少程序的在调用函数上的时间,但是却会增加可执行文件的大小。可以说是以空间换时间的操作了。
对于C++而言,并不建议使用宏,而是推荐使用const,因为宏不会进行检查。
auto
#include <iostream>
int main(void)
{
int a = 10;
auto b = a;
return 0;
}
声明auto类型的变量会根据赋值的变量的类型自动推导相应的类型。
b 会根据 a 的 int 类型自动推出 b 的类型也是 int 。
我觉得挺方便的。
还有
#include <iostream>
int main(void)
{
int a = 10;
auto b = a;
int& c = a;
auto d = c;//b是int类型//相当于int d=c;
auto& e = c;//e是int& //相当于int& e=c;
}
引用并不是一个类型,c 虽然是引用 a ,但其还是 int 类型
范围循环
#include <iostream>
int main(void)
{
int arr[] = { 1,2,3,4,5 };
for (auto a : arr)
std::cout << a << " ";
std::cout << std::endl;
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
std::cout << arr[i]<<" ";
return 0;
}
可以达到同样的效果。
再来看看这个
#include <iostream>
int main(void)
{
int arr[] = { 1,2,3,4,5 };
for (auto a : arr)
a *= 2;
for (auto a : arr)
std::cout << a << " ";
std::cout << std::endl;
for (auto& a : arr)
a *= 2;
for (auto a : arr)
std::cout << a << " ";
std::cout << std::endl;
return 0;
}
打印效果
这就是,将数组中每个元素赋值给变量 a,int a=arr[i]
,再对 a自乘一个2,却不会影响数组中的元素
而
这个用的是引用,相当于int& a=arr[i]
,直接可以作用于数组元素。会改变数组元素
这些都是从C过度到C++的基础知识点,比较零碎,但都需要了解。
谢谢观看
如有问题,烦请大佬指点一二。
以上是关于由C过渡到C++-入门知识点的主要内容,如果未能解决你的问题,请参考以下文章
[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础