文章目录
简介
本篇文章学习c++知识,主要是补充C语言的不足之处,同时也为后续C++学习铺路。
命名空间
命名空间来历
C/C++中 变量,函数都是大量存在的,都是存在全局作用域中,且看如下代码:
#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
return 0;
}
报错了,因为rand是包含在stdlib.h中的函数,用来获取随机值,但现在把他当作变量,所以错了。
所以用到命名空间
要用到namespace关键词,后面加命名空间名字和{},记得不用加分号
namespace jibin
{
int rand=15;
int Add(int a, int b)
{
return a + b;
}
struct st
{
int a;
char c;
};
}
命名空间相当于开辟了一个全新的作用域,里面的东西都局限该域
命名空间使用
- 第一种 命名空间的名字加 : :
int main()
{
int a = 2, b = 3;
int sum = jibin::Add(a, b);
printf("%d", sum);
return 0;
}
相当于专门去访问该域里的Add ,现在就可以编译运行了
- 第二种 using +命名空间的某个成员
using jibin:: st ;
int main()
{
int a = 2, b = 3;
int sum = jibin::Add(a, b);
st k;
k.c = 'm';
printf("%d", sum);
printf("%c", k.c);
return 0;
}
- 第三种 全部展开
using namespace jibin;
int main()
{
int a = 2, b = 3;
int sum = jibin::Add(a, b);
st k;
k.c = 'm';
printf("%d", sum);
printf("%c", k.c);
return 0;
}
C++输入输出函数
学习一门新计算机语言,第一件事肯定输出 Hello World,
- 使用cin cout函数,1.必须包含iostream头文件,2.以及使用上述方法展开命名空间std
- "<<"是流插入运算符,“>>"是流提取运算符,endl表示换行,
- 使用该函数非常方便,他们自动识别类型格式,不用再像scanf,printf手动调整,
- 早期c++包含.h,所有的功能都在全局域实现,后来std发展,为了和c去分就去掉.h了
#include <iostream>
using namespace std;
int main()
{
int a;
cin>>a;
cout << "Hello World!" << endl;
}
std使用问题
- 日常学习直接全部展开即可,using namespace std;
- 但是全部展开,以后定义变量就可能和std库里重合了,但是日常学习代码量少,问题不大,但如果要做项目代码量大就容易出现冲突,所有项目中最好用std::或者using std::cin,using*std::cout,这种,展开常用即可。
缺省函数
缺省函数是声明或定义函数时,指定一个参数缺省值。 如果没有实参传入,就用缺省值。
#include <iostream>
using namespace std;
void Func(int t=9)
{
cout << t << endl;
}
int main()
{
Func();
Func(5);
return 0;
}
如果传入5就打印5,否则打印函数缺省值9,
分类
- 半缺省函数,顾名思义,函数参数一些缺省,一些不缺省,记住一定要从右往左缺省
#include <iostream>
using namespace std;
void Func(int t,int f=5)
{
cout << t <<" " << f << endl;
}
int main()
{
Func(1,2);
Func(3);
return 0;
}
- 全缺省函数,即每个参数都有缺省值。
#include <iostream>
using namespace std;
void Func(int t=4,int f=5)
{
cout << t <<" " << f << endl;
}
int main()
{
Func();
Func(1,2);
Func(3);
return 0;
}
注意: 1.声明和定义不能都出现缺省值,优先给声明,(万一两处的缺省值不一样)。
2.缺省值必须是常量或全局变量。
函数重载
C语言种如果两个函数名字相同,那就会报错。
但c++中,如果两个函数相同,但参数类型或者数目个数或者类型顺序不同,就能通过。
如果参数相同,但返回类型不同,不够成重载
#include <iostream>
using namespace std;
void Func(int t=4,int f=5)
{
cout << t <<" " << f << endl;
}
void Func(char c)
{
cout << c << endl;
}
int main()
{
Func();
Func(1,2);
Func(3);
Func('m');
return 0;
}
4 5
1 2
3 5
m
函数运作原理
- 预处理:展开头文件 / 宏替换 / 条件编译 / 去掉注释,后缀.cpp变为.i
- 编译:检查代码,生成汇编代码,后缀 .i 变为 .s,如果有函数声明,那相当于承诺,到链接再兑现,如果是函数定义,那直接实现
- 汇编: 汇编代码转为二级制机器码,后缀 .s 变为 .o
- 链接:生成可执行程序,xxx.exe / a.out,找到定义(兑现承诺),
C语言无法区分同名函数,所以不支持重载函数,
但C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。
引用
定义
引用是给一个变量取一个别名,两者共用一块内存,两者关系就像西红柿和番茄的关系 ,引用的存在主要来替代指针作用,符号是&,和取地址符相同。
#include <iostream>
using namespace std;
int main()
{
int a = 15;
int& b = a;
cout<<b<<endl;
cout <<&a<< endl;
cout << &b << endl;
return 0;
}
误区
注意 :
1, 引用类型必须和要引用的类型一致,
2,引用必须要有初始化,
3, 一个变量可以有多个引用,(可以有多个别名),
4,一个引用有对应之后,不能再引用别人,
const int a = 10;
//int& ra = a; a是常量,不能更改
const int& ra = a;
//int& b = 10; 10是常量,
const int& b = 10;
double d = 12.133;
//int& dd = d; 类型不同
const int& ddd = d;
应用
void swap(int& a, int& b)
{
int t = a;
a = b;
b = t;
}
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& t = Add(1, 3);
Add(4, 5);
cout << t << endl;
return 0;
}
这里,t的值是随机值,经过第一个Add函数t是4(指向销毁的c),可是第一个函数结束栈就销毁了,第二函数又进行,顶替了原来c的位置,所以t的值不确定。
传值和传引用效率比较
#include <iostream>
#include <ctime>
struct Arr {
int arr[1000];
};
Arr a;
void Func1(Arr a)
{
return;
}
void Func2(Arr& a)
{
return;
}
Arr Func3()
{
return a;
}
Arr& Func4()
{
return a;
}
using namespace std;
int main()
{
Arr a{ 5,8,9,1 };
const int N = 1000005;
int begin1 = clock();
for (int i = 0;i < N;i++)
Func1(a);
int end1 = clock();
int begin2 = clock();
for (int i = 0;i < N;i++)
{
Func2(a);
}
int end2 = clock();
int begin3 = clock();
for (int i = 0;i < N;i++)
{
Func3();
}
int end3 = clock();
int begin4 = clock();
for (int i = 0;i < N;i++)
{
Func4();
}
int end4 = clock();
cout << end1 - begin1 << endl;
cout << end2 - begin2 << endl;
cout << end3 - begin3 << endl;
cout << end4 - begin4 << endl;
return 0;
}
33
2
72
2
可见 传引用比传值效率明显高很多,因为传引用本质是传地址,而传值要复制,时间花费高。
引用和指针区别
- 从语法概念上讲,引用就是一个别名,没有独立空间,和引用实体共用一块空间,
- 但实际底层上,引用有空间,引用本质上是按照指针方式实现的,
int a = 10;
int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
引用和指针汇编代码对比:
引用和指针区别
1,引用是变量的别名,指针存的是变量地址。
2,引用定义必须要初始化,指针不用,
3,引用初始化引用一个实体后,不能再引用其他实体,而指针可以随时指向同类型实体。
4,没有NULL引用,但有NULL指针,
5,在sizeof中不同,引用大小是引用实体类型的大小,指针始终是地震空间所占大小(32位是4字节,64位是8字节),
6,自加,引用+1就是引用实体加1,指针+1 是指针向后偏移一个类型的大小。
7,没有多级引用,但有多级指针。
8,访问实体方式不同,指针是显式解引用,而引用是编译器自己处理。
9,引用比指针更安全。
内联函数
概念
以inline修饰的函数,在程序编译时会用函数体替换掉原来函数位置,减少栈的开销,提高效率。
特性
1,内联函数是牺牲空间换时间做法, 缺陷就是会使目标文件增大。
2,编译器会自主选择是否进行内联,就算加上inline,编译器也会自主选择是否内联,那种函数短小且被多次调用的而且不是递归就会内联,
3,内联函数定义和声明不能分离,不然导致链接错误,因为inline展开,函数就被函数体替换了,函数地址消失,导致链接找不到函数地址,
#include <iostream>
using namespace std;
inline int Add(int a, int b)
{
a++;b++;
return a + b;
}
int main()
{
int t = 0;
for (int i = 0;i < 1000;i++)
{
t = Add(2, 3);
}
}
如果不内联,反汇编代码行数(忽略其他): 1000+3,(会call函数地址),
内联就是1000*3,将函数体替换过去了,
#define Add(a,b) ((a)+(b))
或者可以用宏代替,
【面试题】
宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
auto
随着c++学习,类型越来越多,复杂,长度也长,所以auto发挥作用,自动识别类型
注意: 编译时,auto会被替换成实际所需类型,
auto 与指针和引用结合
auto当作指针用时,加不加 * 无所谓,但是当引用用时必须加 &
#include <iostream>
using namespace std;
int main()
{
int x = 10;
auto a = &x;
auto* ra = &x;
auto& c = x;
* a = 12;
cout << *ra << endl;
c = 20;
cout << *a << " " << c << endl;
return 0;
}
12
20 20
auto与for循环使用
int arr[] = { 1,5,2,6,8 };
for (auto& x : arr)
{
x *= 2;
cout << x << endl;
}
2
10
4
12
16
auto会获得数组长度,从开头到结尾依次将数组读取到x中,
//错误示例
void test(int arr[])
{
for (auto& x : arr)
cout << x;
}
这种错误,因为数组作参数接受是以指针方式接受的,丢失了长度信息,auto无法确定开头结尾,所以用不了。
nullptr
C语言中NULL实际上代表0,与我们想要“空指针”含义有别,c++中我们用nullptr代替NULL,
使用nullptr时不用包含头文件,null是c++的关键词,
sizeof()大小与sizeof((void*)0)一致是8字节,