💒前言
学习本章内容需要掌握C
语言{类型、循环、函数、结构体、指针
}等内容 建议学完C
再看
希望对从来没有接触过C++
只是听过的小白 有些帮助
——我并不专业,也是正在学习C++
的小学生!
🧸什么是C++
C++是C升级而来,所以C++程序兼容C的大部分代码!
🧸初识C++代码
先看一段C的代码
#include <stdio.h>
int main()
{
printf("hello world!");
}
这段代码很简单,#include <stdio.h>
是printf
函数的头文件,包含头文件,printf
打印函数才可以在屏幕上打印;
并且打印其他类型需要自己指定例如:
int a = 10;
printf("%d\n",a);
下面是C++的代码
#include <iostream>
using namespace std;
int main()
{
cout <<"hello world!"<< endl;//流插入
return 0;
}
解释以下上面的代码,
#include <iostream>
是C++
的库,io
意为输入输出;stream
意为流。
——————可以称之为 输入输出流
using namespace std;
要解释这句 ,你需要先知道命名空间
这个功能,下面会讲
cout <<"hello world!"<< endl;
,cout
:流插入;endl
:换行符;
此句意为 将"hello world!"
字符串流向cout
,也就打印在了屏幕上,后面是将 endl
流向cout
; 后面会详解
🧸命名空间
关键字:namespace
命名空间:可以理解为 变量、函数 的名字的作用域
在C++的库中 都是将函数的实现 封装起来 为了防止命名冲突
用法
namespace N
{
int a = 10;
int Add(int left, int right)
{
return left + right;
}
}
int main()
{
printf("%d\n", a); // 该语句编译出错,无法识别a
return 0;
}
上述代码 是无法识别到a的,因为a 不在main函数中,也不再全局变量中,而是封装在N的命名空间中
main函数找a的顺序:先找局部,再找全局
如何找到a
::
作用域运算符
在a前面加上命名空间 N和作用域运算符::
int main()
{
printf("%d\n", N::a);
}
展开命名空间
关键字:using
展开后 命名空间失效 ,内部代码全部暴露
此时可以解释using namespace std;
是什么意思
展开名字叫std
的命名空间,因为不展开 所有的对象都用不了
也可以展开部分对象
using std::cin;
//展开cin - 流提取,接收变量的 与scanf作用相同
using std::cout;
//展开cout - 流插入 ,打印变量的 与printf作用相同
cout、cin是一个ostream类的对象
对象
类似C中的结构体,在C++中叫对象
🧸输入、输出
你可以复制这段代码 测试一下
#include <iostream>
using namespace std;
int main()
{
int a;
cin>>a;
cout<<a<<endl;
return 0;
}
输入 a 的数值,并打印 ,在c++中打印会自动识别类型
🧸缺省参数
在定义函数时 可以提前给定数值,传参时可以少传参数、或者不传参数
void TestFunc(int a = 0)
{
cout<<a<<endl;
}
int main()
{
TestFunc(); // 没有传参时,使用参数的默认值
TestFunc(10); // 传参时,使用指定的实参
}
全缺省:多个函数参数 都有缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
半缺省:从右向左依次给缺省参数
void TestFunc(int a, int b = 10, int c = 20)
缺省参数不能在函数声明和定义中同时出现
问题: 如果是以下程序 会如何调用?
void Test(int a = 0)
{
cout << a << endl;
}
void Test()
{
cout << 10 << endl;
}
int main()
{
Test();
return 0;
}
上述代码中出现了两个Test
,构成函数重载,在C
中不支持,在C++
中支持 ,函数重载则可以解释
先说结论:报错 Test
对重载函数的调用不明确
如果没看懂,先看下面的,再回来看。
🧸函数重载
同意作用域 可以有多个重名函数,条件是:参数个数、类型、顺序,必须不同
返回类型 不同 不构成函数重载
void Test(double a)
{
cout << a << endl;
}
void Test(int a)
{
cout << a << endl;
}
int main()
{
Test(1.1);
Test(1);
return 0;
}
上述代码,会根据给定的实参去找对应的函数,复制代码试一试
原理
C
不支持函数重载是因为C语言
对函数的命名规则比较简单
C++
在Windows
下的规则过于复杂,不便观看,下面用linux
演示
int Add(int a,double b,int* p)
对于以上代码 C
的汇编 函数名Add
C++
的汇编函数名 _Z3AddidPi
_Z
前缀
3
函数名个数
Add
函数名
i
int类型
d
double类型
Pi
int*类型
回顾编译器,编译的过程
1、预处理
:头文件展开、宏替换、条件编译、去掉注释,生成.i文件
2、编译
:检查语法、生成汇编代码,生成.s文件
3、汇编
:汇编代码转换成二进制机器码 ,生成.o文件
4、链接
:生成.out文件
如果文件中有 函数的实现 则函数产生的名字 直接带有汇编实现地址
如果文件中只有函数声明,定义在顶一个cpp文件中,则连接的时候,到.o生成的符号表里找到函数的定义的地址,才会填写函数的地址
🧸引用
引用符号:&
引用:变量的别名
用法:Type& name;
引用概念
例如:一只修勾叫多多,它原来叫 狗狗 ,起了个名字 叫多多,本质上 都是同一只修勾!
void TestRef()
{
int a = 10;
int& b = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &b);
}
可以看到 a和b的地址相同 值相同,证明b就是a,那有什么用呢? 后面详解。
引用特性
1、必须初始化 int& b;
这样写是不对的
2、一个变量可以有多个引用,int a ;
int &b = a;
int& c = b;
int& d = a;
可以同时存在
3、引用只能引用一个实体 int a;
int c ;
int & b = a;
这样没问题 在写一个int& b = c;
则错误,不能改变。
引用使用场景
1、做参数
:a.提高效率 b.形参修改,改变实参
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
此时,形参改变会影响 实参,因为形参是实参的引用,实际上就是实参,而且不会产生临时变量。
2、做返回值
:a.提高效率 b.修改返回变量
提高效率 是因为,不管事传值,还是传址,都会产生临时变量而拷贝,而引用则不会产生临时拷贝,从而减少工作量
#include N 10;
int& At(int i)
{
static int a(N);
return a[i];
}
for(size_t i = 0;i<N;i++)
{
At(i) = 10+i;
}
加上关键字static
,该变量就被定义成为一个静态全局变量
此时a
是静态变量 不会被销毁 而且返回值是 左值 可以被修改 反回了a[i]这个空间的别名
当函数返回值 出了函数作用域销毁 则不能使用引用做返回值
int& Add(int a,int b )
{
int ret = a+b;
return ret;
}
此时 函数调用完ret
会被回收,可能会造成非法访问
引用权限
权限放大 - 不行
const int a = 10;
int& b = a;
权限不变 - 可以
const int c = 20;
consr int& d = c;
权限缩小 - 可以
int e = 30;
const int& f = e;
`注:`表达式产生的结果是 具有常属性的 所以想要引用则需要加const
int x1 = 1, x2 = 2;
const int& y = x1+x2; 可以
包括整形提升、截断 所产生的的是 临时拷贝的整形提升、截断
double d = 11.11;
int a = d;
a = 11 是 d 的临时拷贝 进行截断产生的
int& i2 = d; 不可以 - 因为临时拷贝有常属性不能改变
const int& i3 = d; 可以 用const修饰后 i3则不能改变 权限不变
引用和指针的区别
在语法
概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层
实现上实际是有空间的,因为引用是按照指针方式来实现的。
Col1 | 引用(渣男) | 指针(专一) |
---|---|---|
意义 | 存变量的地址 | 是变量的别名 |
初始化 | 不一定要初始化 | 必须初始化 |
能随意指向同类型实体 | 只能引用一个实体 | |
空值 | 有NULL指针 | 没有NULL引用 |
大小 | 指针大小固定 | 引用的大小是实体的大小 |
增加 | 指针增加是偏移一个类型的大小 | 引用增加是实体+1 |
总结
:指针使用更为复杂 因为控制不好可能会出现野指针 空指针 ,所以没有引用安全
🧸extern “C” 编译连接C++与C的调用
例如:
1、C调用C++写的库(静态库、动态库)
2、C++调用C写的库(静态库、动态库)
实现方法待填写
…
🧸内联函数 inline
在C中为了 减少函数调用可以使用宏
#define ADD(x,y) ((x)+(y))
来代替 ADD(int x, int y){return x+y;}
但是宏很复杂 例如控制不好括号 容易造成错误
有了inline 则可以代替宏
inline void Func()
{
.假设编译后是10行代码
}
内联函数原理
可以减少函数栈帧,直接替换成代码 不建立栈帧
内联函数使用场景
建议
代码长度小于10 或20行 而且经常调用的函数
危害
:
1、假设内联函数展开是10行 调用1000次 则是10*1000 = 10000行代码
2、假设函数不展开 调用1000次 则是10+1000 = 1010行代码
内联函数结论
短小,频繁调用的函数建议定义成inline
🧸auto 自动填写类型
auto
在类型特别长的时候用起来很好,或者在下面的范围for循环中可以用
int a = 0;
auto b = a;
auto c = 'c';
auto d = 1.1;
🧸基于范围的for循环(C++11)
在C中循环这样写
int main()
{
int a[] = {1,2,3,4,5,6,7,8,9};
int n = sizeof(a) / sizeof(a[0]);
for(int i = 0;i < n ;i++)
{
printf("%d ",a[i]);
}
return 0;
}
在C++中的范围for这样写
int main()
{
int a[] = {1,2,3,4,5,6,7,8,9};
for(auto i:a)
{
cout << i <<" ";
}
cout << endl;
return 0;
}
运行结果如下
这里 auto 可以自动识别 i的类型 ,当然自己写int也可以
范围for赋值呢?
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9 };
for (auto& i : a)
{
i = 10 + i;
}
for (auto i : a)
{
cout << i << " ";
}
cout << endl;
return 0;
}
给i添加个引用符号 ,让i
等于a[]
中的元素,直接改变i
即可!
范围for使用条件
下列代码错误,因为不确定a
的范围
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
🧸空指针nullptr(c++11)
在C
中空指针是 NULL
而NULL
本质 = 0
;而这样在C++
中可能会产生歧义
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
结果为:f(int)
f(int)
f(int*)
也就是NULL 也去调用了第一个f(int),而没有调用f(NULL)
cout << sizeof(nullptr) << endl;
cout << sizeof(void*) << endl;
结果都为4
后续使用都推荐nullptr
🎀结束
本次的入门基础 就结束啦 ,下一章 类和对象!
更多推荐
【C++】入门基础
发布评论