目录
2.using namespace std的作用:解决C语言中变量命名冲突的问题
6-1-1.为什么C语言不支持重载,C++支持?C++是如何支持的?---函数名修饰规则不同
1.C++兼容C的语法
//C++兼容C的语法
//C语言版本hello world
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
//C++版本hello world
#include<iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
2.using namespace std的作用:解决C语言中变量命名冲突的问题
2-1namespace的由来
#include<stdio.h>
int main()
{
int scanf = 10;//对
int strlen = 20;//对
//C语言中标识符命名的两个点:1.不能以数字,下划线开头 2.不能和关键字名字一样(但可以是函数名)
printf("%d", scanf);//对
printf("%d\n", strlen);//对
scanf("%d", &scanf);//错
//本意是第一个scanf用stdio.h里的库函数,第二个scanf用int类型的变量
//但是C语言的局部优先原则,这里的两个scanf都是int类型的变量,所以出错
//小结:如果我就是想要达成我的本意的这个目的,C语言明显做不到(有命名冲突的问题),所以C++就使用namespace命名空间域来完善C
}
这是将int scanf=10;放在了局部,定义int scanf的时候还是可以的,但是在使用scanf("%d",&scanf);时出现错误;
但是如果将scanf放在全局,连定义 都不被允许。namespace的使用:
2-2namespace的使用:
#include<iostream>
//定义的是一个命名空间域:(变量和函数构成)
namespace song
{
//变量
int scanf = 1;
int strlen = 2;
//函数
int add(int a, int b)
{
return a + b;
}
}
int main()
{
//默认访问的是先局部后全局(局部没有则采用局部的)
printf("%x\n", scanf);
printf("%x\n", strlen);
//指定访问song命名空间域
printf("%x\n", song::scanf);
printf("%x\n", song::strlen);
printf("%d\n", song::add(10,20));
}
::是域作用限定符,限定是属于哪一个域的变量或者函数
常见的域有:局部域,全局域,命名空间域,类域
#include<iostream>
//全局域
int a = 10;
//命名空间域可以嵌套
namespace song
{
int a = 20;
namespace huang
{
int a = 30;
}
namespace chen
{
int a = 40;
}
}
//类域
class stu
{
public:
int a = 50;
};
int main()
{
printf("默认先局部后全局(局部没有,全局有):a=%d\n", a);
printf("指定song命名空间域:a=%d\n", song::a);
printf("指定song命名空间域里的haung命名空间域:%d\n", song::huang::a);
printf("指定song命名空间域里的chen命名空间域:%d\n", song::chen::a);
stu s;
printf("类域:%d\n", s.a);
return 0;
}
备注:
- 同一个项目的不同文件里 可以使用相同名称的命名空间域,编译链接时会自动合并
- 但是在同一个域中不能定义相同的标识符
3.使用标准库或自己定义的命名空间里的东西的三种方式:
#include<iostream>
int main()
{
//要使用标准库里的东西,有三种方式:
//方式1:每一个都指定命名空间
//麻烦但是最标准
std::cout << "hello world1" << std::endl;
//方式2:整个东西都在全局展开,一夜回到解放前
//方便,但是当我们自己定义的和标准库里的东西名字相同,发生命名冲突的时候就没办法解决了
using namespace std;
cout << "hello world2" << endl;
//方式3:折中办法,对于标准库中的部分常用进行展开
using std::cout;
using std::endl;
cout << "hello world3" << endl;
return 0;
}
给大家看看使用自己定义的东西也是有三种方式 :
4.C++中的输入和输出
cout现在讲不清楚,我们的储备知识还不够,先记住使用即可
//ostream 类型全局对象 cout
//istream 类型全局变量 cin
//endl 全局的换行符号
#include<iostream>
using namespace std;
int main()
{
int a = 0;
cin >> a;
//自动识别类型:原理就是函数重载和运算符重载
cout << a<< endl<< &a << endl;
return 0;
}
5.缺省参数(缺省==不省==写上==默认)
缺省参数是指在声明和定义函数的时候为函数的参数设定一个默认值,在函数调用的时候,如果没有指定实参则采用该默认值.(备胎)
缺省:迷惑的计算机术语之一
5-1缺省参数的分类
#include<iostream>
using namespace std;
//缺省参数的分类
//1:全缺省
void test1(int a=10, int b=20, int c=30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl << endl;
}
//2:半缺省
//部分缺省-必须从右往左连续缺省
void test2(int a , int b , int c =10)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
int main()
{
test1(1, 2, 3);
test2(1, 2);//实参个数>=没缺省的参数个数
return 0;
}
缺省参数的意义:(举例说明)
#include<iostream>
typedef struct Stack
{
int* a;
int size;
int capacity;
}Stack;
//老版本:
//void InitStack(Stack* ps)
//{
// ps->a = (int*)malloc(sizeof(int) * 4);
// ps->size = 0;
// ps->capacity = 4;
//}
//新版本:
void InitStack(Stack* ps, int capacity=4)
{
ps->a = (int*)malloc(sizeof(int) * capacity);
ps->size = 0;
ps->capacity = capacity;
}
int main()
{
//假设我知道栈内至少需要存100个数据
//如果现在将上面的capacity写成100,下面我ST2就没办法使用了
//如果现在将上面的capacity写成10,在这里就要扩容,扩容是有时间成本的
Stack ST1;
//老版本:InitStack(&ST1);
//新版本:
InitStack(&ST1, 100);//传了,使用传的100
//假设我知道栈内至少需要存10个数据
Stack ST2;
//老版本:InitStack(&ST2);
InitStack(&ST2, 10);//传了,使用传的10
//假设我不知道栈内至少需要存多少个数据
Stack ST3;
InitStack(&ST3);//不传,使用备用的4
return 0;
}
备注: 缺省参数不能在函数声明和定义中同时出现,否则就会报错
最好是在声明时写缺省,也就是下面这样
void InitStack(Stack* ps, int capacity = 4);//声明缺省
int main()
{
//业务需求;
return 0;
}
//新版本:
void InitStack(Stack* ps, int capacity)//定义不缺省
{
ps->a = (int*)malloc(sizeof(int) * capacity);
ps->size = 0;
ps->capacity = capacity;
}
6. 函数重载
函数重载的定义:C++中支持两个函数名相同,但是函数的参数(参数的个数或者类型)要不同
- C语言中一个项目中不允许出现同名函数
- C++中的函数重载允许一个项目中出现同名函数
#include<iostream>
using namespace std;
int Add(int a, int b)
{
return a + b;
}
char Add(char a, char b)
{
return a + b;
}
double Add(double a, double b)
{
return a + b;
}
float Add(float a, float b)
{
return a + b;
}
int Add(int a, int b, int c)
{
return a + b + c;
}
int main()
{
cout << Add(1, 1) << endl;
cout << Add('1', '1') << endl;//函数参数的类型构成重载
cout << Add(1.1, 1.1)<< endl;//函数参数的类型构成重载//!!!备注:如果没强转或者备注,1.1默认就是double类型
cout << Add((float)1.1, (float)1.1)<< endl;//函数参数的类型构成重载//强转
//cout << Add(1.1f, 1.1f)<< endl;//函数参数的类型构成重载//备注
cout << Add(1, 1, 1) << endl;//函数参数的个数构成重载
return 0;
}
这样写简直是为难编译器了!!哈哈🐼
思考:难怪C语言为什么不写交换函数和排序函数的库函数,那是因为C语言不支持函数重载,要还得像qsort一样,一个一个字节地交换,但是这样很不方便。
6-1.面试题:
- 为什么C语言支持函数重载,而C++支持函数重载?
- extern "C'的作用
6-1-1.为什么C语言不支持重载,C++支持?C++是如何支持的?---函数名修饰规则不同
备注:这里由于博主还没有干到LInux,就不能给大佬们演示linux下函数名修饰规则的具体内容了:
C 语言中:
C++中:
6-1-2.extern "C'的作用
6-1-2-1.什么是中间件程序?为什么会有extern "C"?
在写项目的时候,有的时候会用到中间件程序(配合可执行程序的一些组件):
通常我们就会把它编译成静态库或动态库(比如.dll).
如果这个中间件程序是用C++写的,但是整体的程序时用C语言写的,虽然在编译成二进制的指令的时候,C和C语言都没太大差异(因为此时已经经历了各自编译器的编译),但是由于C语言和C++的函数名修饰规则,整体程序在找中间件程序(组件)中的函数的时候就会表示找不到.这时extern "C"的作用就凸显出来了.
6-1-2-2.extern "C"的作用和为什么可以通过extern "C" 解决这个问题?
extern "C" 的作用:让C++用C的函数名规则去找函数地址.
基石:C++兼容C的语法,C++知道C语言的函数名规则,所以在有C和C++的函数名规则冲突的时候,在C++程序中使用extern "C" +函数声明 ,就可以解决这个问题.
6-1-2-3.extern "C"的使用场景举例:
下面以谷歌自己用C++写的tcmalloc代替mallc ,然后写成了一个中间件程序,后来一个C语言程序想用这个中间件程序代替mallc时他遇到的问题和解决办法:
变式: 如果加完了extern "C",有同时有整体C++程序想使用这个被extern "C"修饰过了的中间件.这就可以将这个整体C++程序前加上extern "C".
7.引用
7-1.引用的基本使用(reference)
#include<iostream>
int main()
{
int a = 10;
int& b = a;//b是a的别名,b是a的引用
printf("%d\n", b);
b = 100;
printf("%d\n", a);
}
注意:int& b=a;是取别名
而int* b=&a;是取地址
#include<iostream>
using namespace std;
void Swap(int* m, int* n)
{
int temp = *m;
*m = *n;
*n = temp;
}
void Swap(int& m, int& n)
{
int temp = m;
m = n;
n = temp;
}
int main()
{
int a = 10;
int b = 20;
//传地址交换
Swap(&a, &b);
printf("a=%d\tb=%d\n", a, b);
//传引用交换
Swap(a, b);
printf("a=%d\tb=%d\n", a, b);
return 0;
}
7-2.引用的特性 :
- 一个变量可以有多个别名
- 引用必须初始化(但是指针没有初始化的规定)
- 引用一旦引用了一个实体后,就不能再引用其他实体
int main()
{
int a = 10;
//一个变量可以有多个别名b,c,d
int& b = a;
int& c = a;
int& d = a;
//引用必须初始化
int& e = a;
//不是让e变成r的别名,而是把r赋值给e
int r = 20;
e = r;
printf("%d\n", e);//20
return 0;
}
7-3.常引用
int main()
{
//const权限
const int a = 10;//这里的a是可读不可写
int& ra = a;//错,权限的放大不允许
//错在把可读不可写的变量a给一个可读可写的引用
const int& ra = a;//对
int b = 10;//-可读可写
const int& rb = b;//对,权限的缩小允许-可读不可写
return 0;
}
只要是有类型差异在赋值转换时都会产生临时变量
转换:转换的是中间的临时变量,而不是c
//隐式转换(权限的应用)
int c = 10;
double d = 1.1;
d = c;//对,c语言隐式类型转换,但还是一样是有临时变量(double)类型
//double& rc = c;//错,错因是因为是const double 类型的临时变量给了double类型的变量
const double& rc = c;//对
备注:这里rc引用的不是C,因为类型差异(字节都不一样),rc引用的其实时中间的那个临时变量.
7-4.引用的场景
7-4-1.作参数
7-4-2做返回值(传引用返回)
先看看之前我们学过的传值返回:
传值返回返回的是对象c的拷贝
这里说明了实际上是函数返回值是通过产生一个临时变量(const修饰)来临时保存,然后赋值给ret。
传引用返回:
传引用返回的是对象c的引用
这里返回值是int&,也就是返回的是c的引用,也就是c的别名,然后c的别名又是ret的别名
函数栈帧问题:
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
const int& ret = Add(1, 2);
Add(5, 7);
cout << ret << endl;//12
return 0;
}
下面即是在main函数里没有用ret接收Add(5,7)的返回值,ret还是被改为了12,那是对因为ret是栈上已经销毁的变量c的引用 。
但是如果是传值返回:调用了Add(5,7),还是3
或者把c定义在static,静态常量区上:
越界不一定报错:
1.越界读基本不报错,因为编译器检查不出来
2.越界写,可能报错,而且是抽查,就像查酒驾,一般是查可能性大的地界查--抽查
传引用返回的优点:
因为传值返回传的是对象的拷贝,但是传引用返回是返回的是对象的别名,可以提高效率,这和传值调用和传址调用很像。
指针和引用的异同:
int main()
{
int a = 10;
//语法上,这里是给a起了一个别名,而是新定义了一个符号,并没有额外开空间
int& ra = a;
ra = 20;
//语法上,这里是定义了内存是4个字节的变量存放a的地址
int* pa = &a;
*pa = 20;
return 0;
}
实际从汇编实现的角度,引用的本质类似指针取地址的方式实现的(语法层和底层是隔离开的)---了解即可
指针和引用的不同点::
- 内存开辟角度(概念上)
- 初始化角度
- 实体对象更改角度
- 空指针角度
- 多级指针角度
- 引用更安全角度
8.内联函数
由C语言引入:
//C语言为了避免小函数开辟函数栈帧的开销--->提供了宏,预处理阶段展开
#define Add(x,y) ((x)+(y))
int main()
{
int x = 1, y = 2;
int ret = Add(1, 2);
printf("%d\n", ret);
return 0;
}
C++推荐使用频繁的小函数,定义成inline函数,没有函数的开销,只是在调用的时候展开
内联函数:这里结合了宏没有函数开销的优点,同时又丢弃了宏复杂和不支持调试的缺点。
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int x = 1, y = 2;
int ret = Add(1, 2);
printf("%d\n", ret);
return 0;
}
为什么不是将所有的函数定义成内联函数?(内联的缺陷)
1.因为内联函数的本质是通过通过牺牲展开函数,增加主函数代码量(指令变多,导致编译出来的程序变大,备注:指令变多不一定耗时长)来提高效率,而减少函数调用的开销,从而提高效率的。------>空间换时间所以适合将那些函数内部代码量比较少且频繁被调用的的函数定义成内联。当把大函数定义成内联时,编译器直接不搭理你的定义内联。备注:当调用1000次时,内联展开和调用函数的指令数是截然不同的。
2.内联不建议声明和定义分离,因为内联函数没有地址(直接展开了),会导致链接时找不到。