c++预备知识【简单了解】

  • c++关键字
  • 命名空间
  • c++输入/输出
  • 缺省参数
  • 函数重载
  • 引用
  • 内联函数
  • auto关键字(c++11)
  • 基于范围的for循环(c++11)
  • 指针空值-nullptr(c++11)

在学习c++之前,需要先学习一些c++对于c设计上不合理的地方的优化,如作用域,指针,函数,宏等等

一.关键字

c++98关键字共有63个
image.png
c++11关键字为73个,新增的关键字:alignas,alignof,char16_t,char32_t,constexpr,nullptr,static_assert,thread_local,decltype,noexcept

二.命名空间

在同一个项目组,两个人有可能会取一个同一个变量名或函数名,c语言无法很好的解决命名冲突/命名污染问题,所以c++引入了命名空间的概念。std就是c++标准库的命名空间名,c++将标准库的定义和实现都放到了这个命名空间中

2.1命名空间的定义

命名空间的使用,需要借助namespace关键字,后面跟命名空间的名字(自定义),然后接{},{}内部的名字即为命名空间的成员。

  • 命名空间可以嵌套定义
  • 同一个工程中,可以有多个相同名字的命名空间,编译器最后会合在一个命名空间中
  • 命名空间中可以定义变量,函数,类型
  • 命名空间域只影响使用,不影响生命周期
//namespace 是关键字
//A是命名的名字
namespace A
{
	int rand = 3;
}

namespace B
{
	namespace C
	{
    	int rand = 5;
    }

	int rand = 4;
}


2.2命名空间的使用

编译器找变量:先在局部找,找不到在全局找,如果全局找不到就报错。如果一个变量既在全局定义又在局部定义,要在局部访问全局变量就需要加作用域限定符

#include<iostream>
using namespace std;
int a = 2;
int main()
{
    int a = 3;
    //访问局部a,打印结果:3
    cout << a << endl;
    //访问全局a,打印结果:2
    //左边空白代表直接从全局找,找不到报错
    cout << ::a << endl;
	return 0;
}

命名空间的使用三种方法:

  1. **加命名空间名称以及作用域限定符**

我们可以将全局作用域看作一个命名空间,只是全局作用域的命名空间名字默认为空,如果我们想使用某个命名空间中的名字,只需要将左边的空白替换为该命名空间的名字
编译器直接从std命名空间中搜索

#include<iostream>

int main()
{
    int a = 2;
    //直接从A空间找,找不到报错,打印结果为3
    std::cout << a << std::endl;
    return 0;
}
  2. **全部展开:使用using namespace 名字 引入,**

将std展开到全局,编译器搜索时,会到std命名空间搜索

#include<iostream>
using namespace std;

int main()
{
    int a = 2;
    //直接从A空间找,找不到报错,打印结果为3
    cout << a << endl;
    return 0;
}
  3. **使用using将命名空间中的某个成员引入**

将cout,endl展开到全局作用域

#include<iostream>
using std::cout;
using std::endl;

int main()
{
    int a = 2;
    //直接从A空间找,找不到报错,打印结果为3
    cout << a << endl;
    return 0;
}

三.c++输入/输出

  • c++使用cout(标准输出对象,默认为控制台),cin(标准输入对象,默认为键盘),必须包含头文件iostream,以及使用命名空间std
  • cout ,cin是全局的流对象,endl是特殊的c++符号,表示换行输出,它们都包含在iostream头文件中
  • <<是流插入运算符,>>是流提取运算符
  • cout 和 cin都可以自动识别对象类型
  • cout是ostream类型的对象,cin是istream类型的对象
#include<iostream>
//在平常练习中,全局展开比较方便;但是项目中,建议展开成员
using namespace std;
int main()
{
	int a = 3;
    //自动识别类型
    cin >> a;
    cout << a << endl;
    return 0;
}

四.缺省参数

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。

  • 缺省参数不能在声明和定义同时出现
  • 缺省参数分为全缺省和半缺省
  1. 全缺省
#include<iostream>
using namespace std;
void fun(int a = 3, int b = 3)
{}
int main()
{
    //以下三种方法都可调用fun函数
	fun();
    fun(1);
    fun(1, 2);
    
    return 0;
}
  1. 半缺省
  • 半缺省参数必须是从右向左连续给出的
#include<iostream>
using namespace std;
void fun(int a, int b = 3, int c = 2)
{}
int main()
{
	fun(1);
    return 0;
}

五.函数重载

5.1重载条件

对于c语言,不允许两个函数具有同一个函数名,原因是其函数名修饰规则不支持,而c++支持在同一个作用域下,声明多个功能类似的同名函数。这些函数的形参列表不同,如参数类型,个数,顺序。

  • 返回值类型与函数重载无关
#include<iostream>
using namespace std;
int Add(int num1, int num2)
{
    return num1 + num2;
}
double Add(doubel num1, double num2)
{
    return num1 + num2;
}
void fun()
{
    cout << endl;
}
void fun(int a)
{
    cout << a << endl;
}

int main(void)
{
    Add(2, 1);
    Add(2.1, 2,1);
    fun();
    fun(1);
    return 0;
}
5.2重载原理

代码经过:预处理,编译,汇编,链接形成可执行文件,在汇编时,会生成符号表,符号表里面存放有函数名以及其地址,编译器会根据函数调用处的函数名,找与其函数名相同的函数地址并且存放到符号表中,如果是多文件的话,会在链接时找,找不到会报链接错误。调用处的函数名和定义处的函数名是会被编译器修饰的。这就是函数名修饰规则。由于vs的函数名修饰规则比较复杂,所以使用Linux来观察函数名修饰规则。
下面是c++代码,使用g++编译

  • objdump -S 可执行文件名 该命令可以查看信息

image.pngimage.png
可以看到第一个函数名被修饰为:_Z3funi
第二个函数名被修饰为:_Z3funpi
第三个函数名被修饰为:_Z3funv

  • 这里的_Z是前缀,一种格式
  • 3是函数名的长度
  • fun是函数名
  • i是int ,pi 是int* , v是void

在函数调用处,编译器也会生成一个这样的修饰名,然后编译器会根据修饰名来找对应的重载函数,
这里也可以看出为什么返回值不能作为重载条件了,因为在函数调用处,不能确定返回值的类型。
而c语言的函数名修饰规则就简单:
image.pngimage.png
这里c的函数名修饰规则就是函数名,所以我们也可以理解为什么c不支持函数重载了,因为c只会根据函数名找,而与参数无关,同名函数无法区分

六.引用

6.1引用概念

在语法层面,引用是给已存在的变量取了个别名,编译器不会为引用变量开辟空间,它和它引用的变量共用一个空间。

  • 可以给变量的引用起别名
  • 引用必须初始化
  • 一个变量可以有多个引用
  • 引用一旦初始化,不能在引用其他实体
#include<iostream>
using namespace std;
int main(void)
{
    int a = 0;
    int& b = a;
    int& c = b;
    int& d = a;

    return 0;
}
6.2常引用

如果引用实体具有常量属性,那么我们使用引用时必须要加const。权限:读+写,常量属性只具有读权限,不具有写权限。如果我们不加const,那么引用就具有了写权限,这种叫做权限的放大,权限不允许放大,但是可以缩小或者平移

#include<iostream>
using namespace std;
int main()
{
    const int a = 3;
    //权限放大,会报错
    //int& b = a;    
    const int& b = a;
    
    //权限放大会报错
    //int& c = 1;
    const int& c = 1;

    double d = 1.1;
	//权限放大,此时会产生临时变量,用来保存类型转换为int的d的值,临时变量具有常性
    //int& e = d;
    const int& e = d;
    
    
    return 0;
}
6.3使用
  1. 引用做输出型参数
#include<iostream>
using namespace std;

void swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;
}

int main(void)
{
    int num1 = 1;
    int num2 = 2;
    swap(num1, num2);

    return 0;
}
  1. 做函数返回值

函数在返回变量时并不会直接返回,而会生成一个临时变量,如果占用空间少的话,就用寄存器来充当。如果大的话,会开辟一个临时变量来保存数据,临时变量具有常属性。
如果函数所返回的对象在函数销毁后仍然存在,则可以使用引用作为返回值。
好处:

  • 可以减少拷贝
  • 调用者可以修改返回对象
#include<iostream>
using namespace std;
/*
int Add(int a, int b)
{
    int c = a + b;
    return c;         //不会直接将c返回,而是生成一个临时变量,保存c的值
}
int main()
{
    //错误,因为临时变量具有常性
    //int& ret = Add(1, 2);
    const int& ret = Add(1, 2);
    return 0;
}
*/
/*
int& Add(int a, int b)
{
    int c = a + b;
    return c;        //返回值类型为引用,可以理解为返回了个c的别名,
}
int main()
{
    //上述操作是未定义的,Add函数栈帧销毁后,c还给操作系统了,但是ret仍然引用c,
    //此时ret的值是未定义的,具体看编译器是如何处理,
    int& ret = Add(1, 2);
    return 0;
}
*/

int& Add(int a, int b)
{
    static int c = a + b;
    return c;     //由于c出来Add作用域后还在,因此不需要拷贝临时变量,用引用返回
}
int main()
{
    int& ret = Add(1, 2);
    return 0;
}

  • 以值作为参数或者返回值类型,在传参或者返回期间,函数不会直接传递实参或者返回变量本身,而是传参或者拷贝一份临时变量,因此在传参或者值返回的效率相比引用低
6.4指针和引用的区别
  1. 从语法上,引用不用开辟空间和实体公用一块空间,而指针需要开辟空间来存放地址
  2. 引用必须初始化,指针不用初始化
  3. 没有空引用,但是有空指针
  4. 引用一旦指定实体,不允许在引用其他实体,而指针可以随意改变指向
  5. 没有多级引用,但是有多级指针
  6. sizeof引用大小为其实体类型的大小,sizeof指针,4或者8个字节
  7. 引用加1就是加1,但是指针加1,其加的是其指向类型的大小
  8. 引用直接使用就行,编译器会自己处理,指针还需要显示解引用
  9. 引用比指针更安全

七.内联函数

c++极其不推荐使用宏:常量宏,函数宏
宏的优点:

  • 提高代码复用性
  • 提高性能

宏的缺点:

  • 不可调试
  • 无类型检查,不够严谨
  • 不可递归
  • 有优先级问题
  • 可能会增大代码量

c++推荐使用const,enum来取代常量宏,推荐使用inline来取代函数宏

7.1内联函数概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

  • 内联函数声明和定义不建议分文件,如果分离会导致链接错误,只有声明,所以编译器会生成call指令,在链接阶段去找对应的函数地址,但是inline函数并不会进入符号表,所以会导致链接错误
  • 内联函数只是一种建议,编译器可以采用也可以不用
  • 如果编译器将函数当作内联函数,则会在编译阶段用函数体代替函数调用语句

八.auto关键字(c++11)

在代码中,有可能会遇到很长很难写的类型,这时可以用typedef重定义。但是typedef有一个缺点:

#include<iostream>
using namespace std;
typedef char* pstr;
int main(void)
{
    //下面变量定义哪个会出错?
    //const pstr p1;              //该变量定义会出错,因为const修饰p1,必须初始化
    const pstr p1 = nullptr;
    const pstr* p2;
    
    cout << typeid(p1).name() << endl;
    cout << typeid(p2).name() << endl;
    return 0;
}

在编程时,常常需要把表达式的值赋值给变量,这就要求在声明变量的时候清楚地知道表达式的类型。然而有时候要做到这点并非那么容易,因此C++11给auto赋予了新的含义

8.1auto性质

早期c/c++中,auto是定义自动存储器的局部变量。c++11中,auto是一个新的类型提示符来指示编译器,auto声明的变量类型是由编译器推导而来的。

  • auto变量必须初始化,编译器会在编译期间将auto替换为实际类型。
  • auto可以与指针或者引用结合使用,使用auto声明引用类型时,必须加&,声明指针时都一样
  • 使用auto在同一行声明多个变量时,变量类型必须一致
  • auto不能作为函数参数,因为编译器不能推导形参的实际类型
  • auto不能声明数组

九.范围for(c++11)

c++11新增了一种遍历数组的方法:

#include<iostream>
using namespace std;

//注意:下面函数的范围for是错误的,因为num实际上是一个指针,不是一个数组
void fun(int num[])
{
    for (auto e : num)
    {
        cout << e << ' ';
    }
}

int main(void)
{
    int num[] = {1, 3, 5, 2, 6};
    // auto 也可以换为int
    // e  : 也可以随意换名字
    for (auto e : num)
    {
        cout << e << ' ';
    }
    cout << endl;

    for (auto& e : num)
    {
        e *= 2;
        cout << e << ' ';
    }
    cout << endl;
    return 0;
}

十.nullptr关键字(c++11)

在c中,NULL是一个宏,NULL的实现如下:可以看到,在c++中,NULL变成了一个int,有些场景会出错,因此c++11,打了个补丁,新增了nullptr作为指针空值,所以以后最好使用nullptr

/*#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif */

//下面这种场景,NULL会出错
void f(int)
{
 cout<<"f(int)"<<endl;
}
void f(int*)
{
 cout<<"f(int*)"<<endl;
}
int main()
{
 f(0);
 f(NULL);          //实际上等价于f(0)        
 f((int*)NULL);
 return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值