《C++深度解构01》C++入门基础
文章目录
一、 第一个C++程序
经典Hello World实操
#include <iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
二、C++核心语法扩展
1.命名空间(namespace)
1)为什么需要命名空间?
在实际生产中,一个大的项目需要分成众多小项目来合作完成,最后代码结合在一起从而实现某个功能,每个人代码中的命名习惯不同,就会产生命名重复的问题。
通过namespace划分作用域,每个函数去找属于自己域的变量,这样即使命名重复也不会出现问题。
看例子:
这里可以看到在同一作用域内,变量名会发生冲突从而导致出错。 而祖师爷发明的namespace此时就可以发力了
2)命名空间定义及格式
- namespace命名空间实际上是划定了与全局域独立的一个域,各个域之间相互独立,所以不同的域中可以定义同一个变量名。
- C++中有函数局部域,全局域,命名空间域,类域
- namespace只能定义在全局内,不能定义在函数内部,但是域内还可以嵌套定义
- C++标准库都放在std的命名空间中
namespace A
{
int a = 10;
}
namespace B
{
int a = 20;
}
int main()
{
//指定命名空间访问
printf("%d", A::a);
printf("%d", B::a);
return 0;
}
3):: 域作用限定符
:: 域作用限定符
作用:指定去找哪个“域”里的东西:
:: 就是 C++ 里的“导航定位符”,用来明确指出某个名字属于哪个作用域。
不给 :: 时,默认从全局中寻找
在全局域中和命名空间域中,含有相同变量名时,如果不指明具体使用哪个,优先使用全局域中变量值
-
嫌麻烦可直接全局展开(如 using namespace std;),但容易命名冲突,冲突风险大,项目不推荐,日常练习可以使用。
-
或者选择局部展开。
//局部展开
#include <iostream>
using A::b;
namespace A
{
int a = 10;
int b = 20;
}
int main()
{
printf("%d", b);
return 0;
}
局部展开时不需要加namespace,直接写命名空间名即可,如果是完全展开则需要加上namespace
使用using展开,命名空间内的元素相当于放置在了全局变量中,在平时的练习中使用会更加方便,但是实际生产中不会展开,会有冲突问题。
2.C++输入与输出(cin&cout)
在hello world代码中我们可以观察到这样一个头文件
这和C语言中的#include<stdio.h>作用一致,都是标准输入输出库 头文件
作用:
- std::cout——向屏幕打印 \ 输出数据(C语言中的printf)
- std::cin——用于接收键盘数据(C语言中的scanf)
- std::endl——换行符与\n相似
1)流插入运算符 (<<)
与C语言中的左移右移运算符一致
#include <iostream>
int main()
{
int a = 10;
//C语言写法
printf("%d\n", a);
//C++写法
std::cout << a << std::endl;
return 0;
}
<<指向cout,表示a流向cout(屏幕),输出打印
在c语言中打印需要手动输入格式 “%d” “%s”…但是在C++中无需手动输入格式,会自动识别格式并输出,而且C++程序包含C语言所以printf可以使用。
2)流提取运算符(>>)
#include <iostream>
int main()
{
int a = 0;
std::cin >> a;
std::cout << a << std::endl;
return 0;
}
>>指向cin,表示从cin(键盘)流入a
同理cin函数也不需要手动输入格式,直接输入则可以自动识别,但是如果输入格式不正确,如图中a为整型,我们输入字符‘c’时,会识别失败,从而放弃对a的写入。
3.缺省参数
在函数定义时就给一个初始值,在调用函数时如果不给定参数则会直接使用定义时的初始值
#include <iostream>
int Add(int x = 10, int y = 20,int z = 30)
{
return x + y + z;
}
int main()
{
//全缺省(不传参)
int re1 = Add();
std::cout << re1 << std::endl;
//全缺省(传参)
int re2 = Add(1, 2, 3);
std::cout << re2 << std::endl;
return 0;
}
re1 = 60
re2 = 6
在re1中,没有给Add传参数,所以直接使用定义时的初始值,在re2中传入参数则正常利用传入参数计算
全缺省和半缺省
- 全缺省:整个函数都不传入参数时,全部利用定义时的初始值计算,若传入参数则利用传入参数进行计算
- 半缺省:在函数定义时的形参中不完全给全
注意事项:
- 缺省值必须从右至左连续提供(不能跳跃)
正确:int Add(int x, int y = 20, int z = 30 );
错误:int Add(int x = 20, int y, int z = 30 );
- 传入参数必须从左至右(不能跳跃)
正确:Add(1, 2, 3);
错误:Add(1, , 3);
4.函数重载(Function Overloading)
在C语言中,任何类型的变量、函数名也不能命名冲突,但是C++中对此进行了巨大改变,同名的函数可以多次定义
函数重载定义: 同名函数的不同形态
Add函数被多次定义,但是很明显函数内的参数都各有不同(个数不同,顺序不同,类型不同)
创建重载函数时的三个规则
- 参数个数不同
- 参数顺序不同
- 参数类型不同
注意事项:
1.返回值不同不能作为重载函数的条件!!!(因为函数在调用时,不会区分想用哪个)
2.只有一个形参的函数不能再同时定义一个没有参数的函数,因为构成缺省参数,编译器会不知道调用哪个
//构成缺省参数
void f()
{
cout << "f()" << endl;
}
void f(int x = 10)
{
cout << "f(int x)" << endl;
}
int main()
{
f();//报错编译器不知道调用哪个函数
return 0;
}
三.C++关键语法特性
1.引用(&)
1)常规引用
概念: 给已存在的变量取 “ 别名 ”,语法概念上不开空间
使用方法:(如图所示)
直接在新定义的变量名前加一个&(C语言中为取地址符号,在这里通用),从这里还可以看到,引用变量和被引用变量的地址是同一块,那么就可以得出,引用不会为其单独开辟空间
作为新的功能出现在C++中,那么他一定是解决了之前存在的问题,那么引用(&)是解决了C语言中的什么问题呢?
优点:
方便传参的使用,在C语言中,若不传入指针,那么形参的改变并不会改变实参,但是传入指针又会显得麻烦,容易弄混指向问题,但是引用变量与被引用变量使用同一块空间,所以形参用引用来接受便可以直接对实参进行改变。
引用来承接参数就可以直接使用,不会存在忘记解引用的问题
注意事项:
1.被引用变量必须初始化
2.一个变量可以有多个引用(即一个变量可以有多个别名)
3.一个引用一旦与被引用变量捆绑则不能改变被引用对象(指针可以改变指向,但是引用不可以)
2)const引用
可以引用一个const对象,但是必须使用const引用(权限相同)
const引用也可以引用普通变量(权限不可以放大,但是可以缩小)
权限放大问题
被const修饰的变量必须使用const引用来引用,因为被cosnt修饰,所以无法改变i的值
权限缩小问题
普通变量可以被const引用,相当于限制了引用 j 的访问权限,但是 i 的访问权限不变。
临时变量与其常属性
注意事项:
- 表达式计算结果:如 a + b;
- 函数返回值:按值返回 (return Value)时
- 隐式类型转换:不同类型之间的赋值或传递(如double类型值赋值给int类型)
以上三种情况都会产生临时变量,具有常属性(右值性),所以在引用时也需要const引用
//表达式计算结果错误示范
int main()
{
int a = 10;
int b = 20;
int& c = a + b;
return 0;
}
a + b的表达式结果并不是直接赋值给引用c的,而是放入一个临时变量中,再将临时变量的值拷贝给c,从而达到赋值操作
a + b具有常属性,但是左值引用c的值可以被修改,左值可被修改,右值无法修改故产生问题
//函数返回值错误示范
int Add(int& a, int& b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
int& c = Add(a, b);
return 0;
}
同理Add的返回值也存在于临时变量中,左值可修改,右值无法修改,故报错
//隐式类型转换错误示范
int main()
{
int a = 10;
double& b = a;
return 0;
}

3)引用和指针的关系
相辅相成,各有各的特点,无法替代对方
- 语法概念上,引用不开辟空间;指针需要存储地址,需要开空间
- 引用了一个对象时就不可以在引用其他对象;指针可以改变指向对象
- 引用可以直接访问对象;指针需要解引用访问对象
- 引用对象的大小为被引用对象的大小,而指针的大小始终相同(32位下四字节,64位八字节)
2.inline(内联函数)
在程序运行过程中,调用函数在汇编语言下需要call函数开辟栈帧等操作,而内联函数inline是直接展开代码执行
但是在编译器中,inline内联函数只适用代码短小的函数,代码非常多时,若多次调用函数展开,意味着每次调用的地方都会复制一份完整的函数指令,最终可执行文件会变大**(代码膨胀Code Bloat)**
inline在编译器眼中更像是一个建议,如果代码过长编译器则不会接受你的建议,继续正常开辟栈帧调用函数。
3.nullptr与NULL
- C语言中NULL被宏定义成了((void*)0)
- C++中NULL则被定义成了整型 0
C和C++中的标准不同,有些场景使用NULL会出现问题
本想通过f(NULL)调⽤指针版本的f(void*函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。
这里传入NULL本想让他调用第二个,可是C++的规定使得NULL为整型所以只能调用第二个
在C中还有一个隐藏坑:
void 可以隐式转换成任意类型指针,但是C++中不会隐式类型转换*
// C++
void* p1 = NULL;
int* p2 = p1; // 此时如果 p1 不强转则会报错,需要 int* p2 = (int*)p1;// C(不严格)
void* p1 = NULL;
int* p2 = p1;

在C++中即使强转NULL类型,函数f(int x) 也不能接受值,这里构成了函数重载(参数类型不同)*
所以创造了nullptr,在C++中,他既是空指针,也可以隐式类型转换成任意类型指针。
下期预告:类和对象(上)
更多推荐





所有评论(0)