C++对C的加强(全)

目录

C++对C的加强

命名空间

为什么要使用命名空间

怎么使用命名空间

命名空间的定义

命名空间的使用

使用域解析符 ::

使用using声明

内联命名空间

嵌套命名空间

随时将新的成员加入命名空间

命名空间中 函数的声明和实现分开

无名命名空间

命名空间取别名

使用using申明命名空间中的某几个成员 可用

using 申明某个成员 容易造成名字冲突

using 申明制定成员函数 遇到函数重载

using申明整个命名空间 可以直接通过成员名 使用

C++ 头文件的现状

输入与输出

实用性加强

全局变量检测增强

c++的函数形参必须有类型

如果函数没有参数,建议写void

更严格的类型转换

if和while中可以使用逗号表达式

for循环(基于范围的循环)

register关键字

register关键字的作用

register修饰符在C语言中的几点限制

在C++中依然支持register关键字

三目运算符的增强

使用using定义别名(取代typedef)

结构体类型增强(重要)

​编辑

 .hpp将类定义和实现放在一个文件里

c++新增bool类型

cv属性

const关键字(重要)

volatile关键字

constexpr(常量表达式)

枚举变量

类型推导(auto和decltype)

语法格式的区别

对 cv 限定符的处理

对引用的处理

总结

引用

1.引用的定义

2.引用作为函数的参数

3.引用作为函数的返回值类型(链式操作)

4.常引用

5.引用的本质:常量指针

动态内存分配

为什么初始化要置为空指针

如何避免野指针?养成良好的编程习惯

new关键字与malloc函数的区别

1.new和delete操作基本类型的空间

2.new分配多维数组

3.new和delete操作类的空间

4.new申请对象数组

内联函数

宏函数和内联函数的区别(重要背)

内联函数的注意事项

函数重载(重要)

1.函数重载的条件:(背)

2.函数重载的底层实现原理

函数的缺省参数(重要)

占位参数

类型转换

C语言的隐式转换和显示转换

C++的类型转换运算符

static_cast < type-id > ( expression ) (常用)

dynamic_cast < type-id > ( expression )

const_cast< type-id > ( expression )

reinterpret_cast< type-id > ( expression )


C++对C的加强

如何解决多人协作开发的中产生的命名冲突问题

在c语言开发中,通过static限定全局变量和函数仅在当前文件有效,从而解决变量和函数命名冲突。

在C++中,使用命名空间。

命名空间

为什么要使用命名空间

一个中大型软件往往由多名程序员共同开发,会使用大量的变量和函数,不可避免地会出现变量或函数的命名冲突。当所有人的代码都测试通过,没有问题时,将它们结合到一起就有可能会出现命名冲突。

为了解决合作开发时的命名冲突问题,C++ 引入了命名空间(Namespace)的概念。

命名空间将全局作用域分成不同的部分

不同命名空间中的标识符可以同名而不会发生冲突

命名空间可以相互嵌套

全局作用域也叫默认命名空间

怎么使用命名空间

命名空间的定义
 /**
namespace 是C++中的关键字,用来定义一个命名空间,语法格式为:
namespace name
{
    变量
    函数
    类
}
name是命名空间的名字,它里面可以包含变量、函数、类、typedef、#define 等,最后由{ }包围 
**/
// 定义一个命名空间,名字叫NameSpaceA
namespace NameSpaceA
{
    int a = 0;
}
 
// 命名空间的定义可以嵌套
namespace NameSpaceB
{
    int a = 1;
 
    namespace NameSpaceC
    {
        struct Teacher
        {
            char name[10];
            int age;
        };
    }
}
 
命名空间的使用
使用域解析符 ::
// 在这里用using声明了 NameSpaceA::a, 它的意思是在声明以后的程序中如果出现
// 未指明命名空间的a,就使用NameSpaceA命名空间里的a
// 如果要使用NameSpaceB命名空间中的a,则仍需要使用这样的方式 NameSpaceB::a;
using NameSpaceA::a;
a = 20;                // 使用命名空间NameSpaceA中的a
NameSpaceB::a = 30;    // 使用命名空间NameSpaceB中的a
使用using声明
// using 声明不仅可以针对命名空间中的变量或者函数,还可以对整个命名空间进行声明
// 这样的方式声明命名空间以后,在后面使用未指定具体命名空间的变量或者函数产生命名冲突的时候
// 默认使用 NameSpaceB中的变量和函数
using namespace NameSpaceB;
a = 10;
 
printf ("%d\n", NameSpaceB::a);
内联命名空间

#include <iostream>
using namespace std;
namespace A {
    namespace V1 {
        void print()
        {
            cout << "hello world!" << endl;
        }
    }
    namespace V2 {
        void print(){
        cout << "welcome to jscet!\n";
        }
    }
    inline namespace V3{
        void print() {
        cout << "最新版本\n";
        }
    }
}

int main()
{
    //新版本
    A::print();
    //旧版本
    A::V1::print();
    A::V2::print();
    return 0;
}

嵌套命名空间

随时将新的成员加入命名空间

命名空间中 函数的声明和实现分开

无名命名空间

无名命名空间 只能在 本源文件使用

命名空间取别名

使用using申明命名空间中某几个成员 可用

using 申明某个成员 容易造成名字冲突

using 申明制定成员函数 遇到函数重载

using申明整个命名空间 可以直接通过成员名 使用

加作用域解决冲突

C++ 头文件的现状

1) 旧的 C++ 头文件,如 iostream.h、fstream.h 等将会继续被支持,尽管它们不在官方标准中。这些头文件的内容不在命名空间 std 中。

2) 新的 C++ 头文件,如 iostream、fstream 等包含的基本功能和对应的旧版头文件相似,但头文件的内容在命名空间 std 中。

注意:在标准化的过程中,库中有些部分的细节被修改了,所以旧的头文件和新的头文件不一定完全对应。

3) 标准C头文件如 stdio.h、stdlib.h 等继续被支持。头文件的内容不在 std 中。

4) 具有C库功能的新C++头文件具有如 cstdio、cstdlib 这样的名字。它们提供的内容和相应的旧的C头文件相同,只是内容在 std 中。

        可以发现,对于不带.h的头文件,所有的符号都位于命名空间 std 中,使用时需要声明命名空间 std;对于带.h的头文件,没有使用任何命名空间,所有符号都位于全局作用域。这也是 C++ 标准所规定的。

输入与输出

换行用于刷新缓冲区,比如printf和cout需要换行来刷新才能输出

使用clog输出日志信息,不需要换行就能输出

cerror用于输出错误信息,也要加换行

实用性加强

C语言为弱语法语言,某些类型不匹配但不会发生编译错误;C++为强语法语言,必须严格类型匹配。

全局变量检测增强

c++的函数形参必须有类型

c语言:允许函数形参无类型(可以传任意参数)

c++不允许

如果函数没有参数,建议写void

c语言:可以 c++不可以

更严格的类型转换

if和while中可以使用逗号表达式

for循环(基于范围的循环)

for_each用法

#include<algorithm>
#include<iostream>
#include<vector>

void func(int n)
{
    std::cout << n << std::endl;
}

int main()
{
    std::vector<int> arr;
    arr.push_back(1);
    arr.push_back(2);

    std::for_each(arr.begin(), arr.end(), func);

    return 0;
}

程序执行结果为:

12

C++11语法格式的 for 循环还支持遍历用{ }大括号初始化的列表

#include <iostream>
using namespace std;

int main() {
    for (int num : {1, 2, 3, 4, 5}) {
        cout << num << " ";
    }
    return 0;
}

程序执行结果为:

1 2 3 4 5

在使用新语法格式的 for 循环遍历某个序列或数组时,如果需要遍历的同时修改序列中元素的值,实现方案是在参数处定义引用形式的变量

#include <iostream>
#include <vector>
using namespace std;

int main() {
    char arc[] = "abcde";
    vector<char>myvector(arc, arc + 5);
    //for循环遍历并修改容器中各个字符的值
    for (auto &ch : myvector) {
        ch++;
    }
    //for循环遍历输出容器中各个字符
    for (auto ch : myvector) {
        cout << ch;
    }
    return 0;
}

程序执行结果为:

bcdef

register关键字

register关键字的作用

register修饰暗示编译程序相应的变量将被频繁使用的变量尽可能的将这个变量保存在CPU内部寄存器中而不是通过内存寻址来访问,是为了提升它的运行速率。

register修饰符在C语言中的几点限制

(1)register变量必须是能被CPU所接受的类型。

这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

(2)因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。

(3)只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。

在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。

(4)局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;

(5)由于寄存器的数量有限(不同的cpu寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

在C++中依然支持register关键字

1、C++编译器有自己的优化方式,不使用register也可能做优化

2、C++中可以取得register变量的地址

C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。

早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充。

void func()
{
    register int iRegister;
    iRegister = 90;
    cout << iRegister << endl;
}

三目运算符的增强

C语言三目运算符  ? : 如果条件为真,返回表达式1,否则返回表达式2 例如: int t = (x < y) ? x + y: x - y;

C++中三目运算符

#include <stdio.h>
 
// C语言中表达式的结果 放在什么地方? ==> 寄存器
// 1
// 表达式返回的是一个值,是一个数
// 在C++中,表达式返回的是变量本身
 
// 2 如何做到的
// 让表达式返回一个内存空间..内存的首地址 指针
// 在C语言中如何实现C++的效果
 
// 3 本质
// C++编译器自己做了取地址的操作
int main()
{
    int a = 10;
    int b = 20;
 
    // C++中三目运算符返回的是变量本身,所以可以作为左值使用
    (a > b ? a : b) = 90;
 
    // 在C语言中让三目运算符可以当左值使用,可以通过返回变量地址实现
    *(a > b ? &a : &b) = 90;
    printf ("%d, %d\n", a, b);
 
    return 0;
}

1)C语言返回变量的值 C++语言是返回变量本身

C语言中的三目运算符返回的是变量值,不能作为左值使用

C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方

2)注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用

(a < b ? 1 : b )= 30;

3)C语言如何支持类似C++的特性呢?

====>当左值的条件:要有内存空间;C++编译器帮助程序员取了一个地址而已

使用using定义别名(取代typedef)

传统使用typedef来重命名类型

C++增加使用using来重命名类型

结构体类型增强(重要)

 

1.定义和使用

C语言中:对象必须通过struct+结构体名来定义

#include <stdio.h>

struct Stu{
    char name[20];
    int age;
};

int main(int argc, char const *argv[])
{
    struct Stu stu1={"小明",20};
    printf("stu1: %s age: %d\n", stu1.name, stu1.age);
    return 0;
}

C++中:使用结构体名可以直接定义对象

#include <iostream>
using namespace std;

struct Stu{
    char name[20];
    int age;
};

int main(int argc, char const *argv[])
{
    Stu stu1={"小明",20};
    cout << "stu1: " <<stu1.name<<" id :"<<stu1.age<<endl;
    return 0;
}

2.C++允许函数作为结构体的成员

3.引入权限修饰符

使用struct修饰,成员默认为公共属性

 

 

使用class修饰,成员默认为私有属性,不可通过外部直接访问

可以通过公共成员间接访问内部私有成员

#include <iostream>
using namespace std;

class Stu{
private:
    char name[20];
    int age;
public:
    void printf();
};

void Stu::printf()
{
    cout << "stu1: " <<name<<" id :"<<age<<endl;
}

int main(int argc, char const *argv[])
{
    Stu stu1;
    stu1.printf();
    return 0;
}

4.C++允许类的成员在定义时赋值

#include <iostream>
using namespace std;

class Stu{
private:
    char name[20]={0};
    int age=10;
public:

    void printf();
};

void Stu::printf()
{
    cout << "stu1: " <<name<<" id :"<<age<<endl;
}

int main(int argc, char const *argv[])
{
    Stu stu1;
    stu1.printf();
    return 0;
}

5.this指针

#include <iostream>
#include <stdlib.h>
using namespace std;

class Stu{
private:
    char name[20]={0};
    int age=10;
public:
    void init();
    void printf();
};

void Stu::init()
{
    this->age=19;
    memcpy(this->name,"小明",sizeof("小明"));
}
void Stu::printf()
{
    cout << "stu1: " <<name<<" id :"<<age<<endl;
}

int main(int argc, char const *argv[])
{
    Stu stu1;
    stu1.printf();
    stu1.init();
    stu1.printf();
    return 0;
}

 .hpp将类定义和实现放在一个文件里

#pragma once

class Stack
{
public:
    void init(int max_len = 1024);
    bool push(int num);
    bool pop();
    int get_top();
    bool full();
    bool empty();

private:
    int m_top;
    int *m_stack;
    int m_max_len;
};


void Stack::init(int max_len)
{
    m_stack = new int[max_len];
    m_max_len = max_len;
    m_top = -1;
}

bool Stack::full()
{
    return m_top == m_max_len - 1;
}

bool Stack::empty()
{
    return m_top == -1;
}

bool Stack::push(int num)
{
    if (!full())
    {
        m_top++;
        m_stack[m_top] = num;
        return true;
    }
    else
    {
        return false;
    }
}

bool Stack::pop()
{
    if (!empty())
    {
        m_top--;
        return true;
    }
    else
    {
        return false;
    }
}

int Stack::get_top()
{
    return m_stack[m_top];
}

c++新增bool类型

bool类型拥有两个值, true false

cv属性

「cv 限定符」是 const 和 volatile 关键字的统称:

const 关键字用来表示数据是只读的,也就是不能被修改;

volatile 和 const 是相反的,它用来表示数据是可变的、易变的,目的是不让 CPU 将数据缓存到寄存器,而是从原始的内存中读取。

const关键字(重要)

1、c++和c中的const都是修饰变量为 只读。

2、c语言 严格准许 const修饰的是只读变量,本质是 变量

3、c++的const 会对变量 优化

(1)如果以 常量 初始化const修饰的变量 编译器会将变量的 放入符号常量表中,不会立即给变 量开辟空间

(2)只有当对a 取地址时 编译器才会给a开辟空间(只读变量)

(3)如果以变量 初始化const修饰的只读变量,没有符号常量表,立即开辟空间

(4)如果以const修饰的是自定义类型的变量 也不会有符号常量表,立即开辟空间

(5)c++中尽量使用const代替define

  • const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查

在旧版本 C 中, 如果想建立一个常量, 必须使用预处理器” #define MAX 1024; 我 们定义的宏 MAX 从未被编译器看到过, 因为在预处理阶段, 所有的 MAX 已经被 替换为了 1024, 于是 MAX 并没有将其加入到符号表中。 但我们使用这个常量获 得一个编译错误信息时, 可能会带来一些困惑, 因为这个信息可能会提到 1024, 但是并没有提到 MAX.如果 MAX 被定义在一个不是你写的头文件中, 你可能并不 知道 1024 代表什么, 也许解决这个问题要花费很长时间。 解决办法就是用一个常 量替换上面的宏。

  • const有作用域,而#define不重视作用域,宏不能作为命名空间、结构体、类的成员,而const可以。

volatile关键字

volatile:“不稳定、易变的”:禁止编译器进行任何形式的优化,每次使用都必须“老老实实”取值操作

        以常量初始化const修饰的只读变量可以通过volatile防止编译器进行优化,可以通过访问地址进行变量值修改。

constexpr(常量表达式)

constexptr目的

  • 修饰常量表达式
  • 提升程序执行效率,即常量表达式是在编译阶段执行的
  • 可以修饰变量,对象,函数

表示方式

  • 修饰普通常量
  • 修饰函数
  • 修饰模板
  • 修饰构造函数

区别函数模板和模板函数

关注后两个字:前者是模板,后者是函数

constexptr可以修饰函数模板,函数模板实例化后得到模板函数

(1)区别const,constexptr,常量表达式

const修饰的并不一定是常量表达式

constexptr修饰的一定是常量表达式

常量表达式要在程序编译时执行,普通变量要在程序运行时执行

#include <iostream>
using namespace std;
int main()
{    
    const int a1 = 10;           // a1是常量表达式。 
    const int a2 = a1 + 20;      // a2是常量表达式
    int a3 = 5;                  // a3不是常量表达式
    const int a4 = a3;           // a4不是常量表达式,因为a3程序的执行到达其所在的声明处时才初始化,所以变量a4的值程序运行时才知道。但编译没问题!
    return 0;
}

以上代码可正常编译。

说明了const声明的不一定就是常量表达式!

因为a3不是常量表达式,a4是常量表达式,但是a3需要在a4前执行而且是运行时,因此a4不能在编译时执行,故说明const并不一定是常量表达式。

(2)定义普通常量与const等价

(3)修饰函数

        a. 必须要有返回值

        b. 返回值如果不是常量表达式,函数修饰符constexptr会被自动忽略

        c. 整个函数的函数体中,不能出现非常量表达式之外的语句,如for循环中声明int i 等(using 指令、typedef 语句以及 static_assert 断言、return 语句除外)。

(4)修饰模板,如果模板函数实例化结果不是常量表达式,修饰符constexptr会被自动忽略

(5)修饰构造函数,只能用初始化列表

(6)constexptr的引用必须绑定到全局变量上

#include <bits/stdc++.h>
using namespace std;
int num1 = 10;
int main(){
    constexpr int& c = num1;
    system("pause");
    return 0;
}

举例

#include <bits/stdc++.h>
using namespace std;
struct Test{
    int id;
    int age;
    //修饰构造函数,必须用初始化列表
    // constexpr Test(){
    //     id = 50;
    //     age = 100;
    // }
    constexpr Test(int idd, int a):id(idd), age(a){}
};
//常量表达式函数
constexpr int func1(){
    constexpr int a = 10;
    int b = 10;
    return b;//返回值既可以是常量表达式也可以是变量,如果是非常量,constexptr会被自动忽略
}
// constexpr void func2(){//error:常量表达式函数需要返回值
//     cout << "无返回值constexptr"<<endl;
// }
template<typename T>
constexpr T display(T t){
    return t;
}
int main(){
    constexpr Test t1{1, 15};
    //constexpr string str = "llx";//Error constexpr无法修饰 string
    constexpr int id = t1.id;
    constexpr int age = t1.age;
    cout << func1() << endl;

    Test t2{2, 20};
    Test res = display(t2);
    cout << "constexptr修饰模板,参数为结构体变量:" << res.id << " " << res.age << endl;

    constexpr Test t3{3, 30};
    res = display(t3);
    cout << "constexptr修饰模板,参数为结构体变量:" << res.id << " " << res.age << endl;
    system("pause");
    return 0;
}

运行结果

具体可参考如下博客

c++nullptr(空指针常量)、constexpr(常量表达式)

const与constexpr的区别

枚举变量

C++基础知识 - 枚举类型_c++ 枚举类型_骆驼胡杨的博客-CSDN博客

类型推导(auto和decltype)

语法格式的区别

auto 和 decltype 都是 C++11 新增的关键字,都用于自动类型推导,但是它们的语法格式是有区别的,如下所示:

auto varname = value;  //auto的语法格式

decltype(exp) varname [= value];  //decltype的语法格式

其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式,方括号[ ]表示可有可无。

auto 和 decltype 都会自动推导出变量 varname 的类型:

auto 根据=右边的初始值 value 推导出变量的类型;

decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。

另外,auto 要求变量必须初始化,也就是在定义变量的同时必须给它赋值;而 decltype 不要求,初始化与否都不影响变量的类型。这很容易理解,因为 auto 是根据变量的初始值来推导出变量类型的,如果不初始化,变量的类型也就无法推导了。

auto 将变量的类型和初始值绑定在一起,而 decltype 将变量的类型和初始值分开;虽然 auto 的书写更加简洁,但 decltype 的使用更加灵活。

请看下面的例子:

auto n1 = 10;
decltype(10) n2 = 99;

auto url1 = "http://c.biancheng.net/cplus/";
decltype(url1) url2 = "http://c.biancheng.net/java/";

auto f1 = 2.5;
decltype(n1*6.7) f2;

对 cv 限定符的处理

「cv 限定符」是 const 和 volatile 关键字的统称:

  • const 关键字用来表示数据是只读的,也就是不能被修改;
  • volatile 和 const 是相反的,它用来表示数据是可变的、易变的,目的是不让 CPU 将数据缓存到寄存器,而是从原始的内存中读取。

在推导变量类型时,auto 和 decltype 对 cv 限制符的处理是不一样的。decltype 会保留 cv 限定符,而 auto 有可能会去掉 cv 限定符。

以下是 auto 关键字对 cv 限定符的推导规则:

  • 如果表达式的类型不是指针或者引用,auto 会把 cv 限定符直接抛弃,推导成 non-const 或者 non-volatile 类型。
  • 如果表达式的类型是指针或者引用,auto 将保留 cv 限定符。

下面的例子演示了对 const 限定符的推导:

//非指针非引用类型
const int n1 = 0;

auto n2 = 10;
n2 = 99;  //赋值不报错

decltype(n1) n3 = 20;
n3 = 5;  //赋值报错

//指针类型
const int *p1 = &n1;

auto p2 = p1;
*p2 = 66;  //赋值报错

decltype(p1) p3 = p1;
*p3 = 19;  //赋值报错

在 C++ 中无法将一个变量的完整类型输出,我们通过对变量赋值来判断它是否被 const 修饰;如果被 const 修饰那么赋值失败,如果不被 const 修饰那么赋值成功。虽然这种方案不太直观,但也是能达到目的的。

n2 赋值成功,说明不带 const,也就是 const 被 auto 抛弃了,这验证了 auto 的第一条推导规则。p2 赋值失败,说明是带 const 的,也就是 const 没有被 auto 抛弃,这验证了 auto 的第二条推导规则。

n3 和 p3 都赋值失败,说明 decltype 不会去掉表达式的 const 属性。

对引用的处理

当表达式的类型为引用时,auto 和 decltype 的推导规则也不一样;decltype 会保留引用类型,而 auto 会抛弃引用类型,直接推导出它的原始类型。请看下面的例子:

#include <iostream>
using namespace std;

int main() {
    int n = 10;
    int &r1 = n;

    //auto推导
    auto r2 = r1;
    r2 = 20;
    cout << n << ", " << r1 << ", " << r2 << endl;

    //decltype推导
    decltype(r1) r3 = n;
    r3 = 99;
    cout << n << ", " << r1 << ", " << r3 << endl;

    return 0;
}

运行结果:

10, 10, 20

99, 99, 99

从运行结果可以发现,给 r2 赋值并没有改变 n 的值,这说明 r2 没有指向 n,而是自立门户,单独拥有了一块内存,这就证明 r 不再是引用类型,它的引用类型被 auto 抛弃了。

给 r3 赋值,n 的值也跟着改变了,这说明 r3 仍然指向 n,它的引用类型被 decltype 保留了。

总结

auto 虽然在书写格式上比 decltype 简单,但是它的推导规则复杂,有时候会改变表达式的原始类型;而 decltype 比较纯粹,它一般会坚持保留原始表达式的任何类型,让推导的结果更加原汁原味。

从代码是否健壮的角度考虑,我推荐使用 decltype,它没有那么多是非;但是 decltype 总是显得比较麻烦,尤其是当表达式比较复杂时,例如:

vector nums;

decltype(nums.begin()) it = nums.begin();

而如果使用 auto 就会清爽很多:

vector nums;

auto it = nums.begin();

在实际开发中人们仍然喜欢使用 auto 关键字,因为它用起来简单直观,更符合人们的审美。

引用

主要作用:提高了程序的运行效率:省去拷贝过程,即空间分配和释放过程

比如在函数按引用传参,不需要传参时变量之间赋值拷贝,也不需要对参数开辟空间和释放。虽然指针也具有同样效果,但是使用指针安全性差,容易造成访问越界和野指针。

1.引用的定义

引用的本质:就是给变量名取个别名

案例1:给普通变量取别名

案例2:给数组取别名

void test02()
{
 int arr[5]={10,20,30,40,50};
 int n = sizeof(arr)/sizeof(arr[0]);
 
 int (&myArr)[5] = arr;
 int i=0;
 for(i=0;i<n;i++)
 {
 cout<<myArr[i]<<" ";
 }
 cout<<endl;
}

案例3:给指针变量取别名

案例4:给函数取别名

注意:

2.引用作为函数的参数

函数内部可以 通过 引用 操作外部变量。

节约空间

3.引用作为函数的返回值类型(链式操作)

链式操作(连续赋值操作):

4.常引用

给常量取别名

void test10()
{ //int &a = 10;//err
 const int &a = 10;//a就是10的别名
  //a = 100;//err
  cout<<a<<endl;
}

不能通过常引用 修改 内容。

常引用 作为函数的参数:防止函数内部修改外部的值。

5.引用的本质:常量指针

int a=10;
int &b = a;//b为a的别名 int * const b = &a;
b = 100;//a的值为100 *b = 100;

动态内存分配

指针在定义时要初始化为空指针,C++中为nullptr是对NULL的封装

为什么初始化要置为空指针

空指针代表0地址,不可以对0地址进行操作

如何避免野指针?养成良好的编程习惯

malloc、free是库函数。new、delete在C++中被抽象为运算符(可以被重载)

new关键字与malloc函数的区别

  • new关键字是C++的一部分,malloc函数是C库提供的函数
  • new以具体类型为单位进行内存分配,malloc只能以字节为单位进行内存分配
  • new在申请单个类型变量时可进行初始化,mallo不具备内存初始化的特性
1.new和delete操作基本类型的空间
  • new 不用强制类型转换
  • new在申请空间的时候可以 初始化空间内容

2.new分配多维数组

3.new和delete操作类的空间

malloc 不会调用构造函数 free 不会调用析构函数

new 会调用构造函数 delete 调用析构函数

4.new申请对象数组

内联函数

内联函数:在编译阶段 将内联函数中的函数体 替换函数调用处。避免函数调用时的开销。

内联函数 必须在定义的时候 使用关键字inline修饰, 不能在声明的时候使用inline

//函數声明的时候 不要使用inline
int my_add(int x, int y);
void test01()
{
    cout<<my_add(100,200)<<endl;
}
//内联函数 在定义的时候使用inline
inline int my_add(int x, int y)
{
    return x+y;
}

内联函数作用:内存空间换运行时间

宏函数:用编译时间换内存空间

宏函数内联函数的区别(重要背)

  • 宏函数和内联函数 都会在适当的位置 进行展开 避免函数调用开销。

宏函数的参数没有类型,不能保证参数的完整性。

内联函数的参数有类型 能保证参数的完整性。

  • 宏函数在预处理阶段展开

内联函数在编译阶段展开

  • 宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员

内联函数有作用域的限制,能作为命名空间、结构体、类的成员

内联函数的注意事项

  • 在内联函数定义的时候加inline修饰
  • 类中的成员函数 默认都是内联函数(不加inline 也是内联函数)
  • 有时候 就算加上inline也不一定是内联函数(内联函数条件)
    • 不能存在任何形式的循环语句
    • 不能存在过多的条件判断语句
    • 函数体不能过于庞大
    • 不能对函数取地址

总结:有时候不加inline修饰 都有可能是内联函数。内不内联 由编译器决定。

函数重载(重要)

函数重载 是c++的多态的特性(静态多态)。

函数重载:用同一个函数名 代表不同的函数功能。

1.函数重载的条件:(背)

同一作用域,函数的参数类型、个数、顺序不同 都可以重载。(返回值类型不能作为重载的条

件)

void printFun(int a)
{
    cout<<"int"<<endl;
}
void printFun(int a, char b)
{
    cout<<"int char"<<endl;
}
void printFun(char a, int b)
{
    cout<<"char int"<<endl;
}
void printFun(char a)
{
    cout<<"char"<<endl;
}
void test02()
{
    printFun(10);
    printFun(10, 'a');
    printFun('a', 10);
    printFun('a');
}

c++中 不能直接将函数名作为函数的入口地址(为啥呢?)

(由重载的底层原理可知)函数名和参数 共同决定函数的入口地址

2.函数重载的底层实现原理

函数的缺省参数(重要)

在函数声明处 给函数参数一个默认的值,如果函数调用处,用户没用传实参,编译器就可以使用 这个默认的值。

注意点:

如果函数的某个参数设置为默认参数, 那么这个参数的右边的所有参数 都必须是默认参数

int func(int a, int b, int c=10);//正确
int func(int a, int b=20, int c);//错误 c必须默认参数
int func(int a=10, int b, int c);//错误 b c必须默认参数
int func(int a, int b=10, int c=20);//正确
int func(int a=10, int b, int c=20);//错误 b必须默认参数
int func(int a=10, int b=20, int c=20);//正确

占位参数

占位参数 也可以是缺省参数(默认参数)

默认参数和函数重载同时出现 一定要注意二义性

类型转换

C语言的隐式转换和显示转换

参考以下博客:

C语言【隐式类型转换】和【显式类型转换】的详解

关于显式类型转换以及隐式类型转换

震惊!关于整型提升不得不说的那些事

C++的类型转换运算符

static_cast < type-id > ( expression ) (常用)

类似于C语言的强制转化,保证代码的安全性和正确性

用途:

  • 相关类型转换:例如整型、实型;还可以将non-const对象转换为const对象,但是它不能将一个const对象转型为non-const对象(只有 const_cast能做到)
  • 子类转父类;
    • 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
    • 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
  • void *指针与其他类型指针之间的转换,但不能对不同类型的指针进行转换

例:

基本数据类型的转换,如

char c1 = 0;
int i = static_cast<int>(c1);
char c2 = static_cast<char>(i);

将空指针转换为目标类型的指针,或反之,如:

int i =0;
int *pa = &i;
void *pv = static_cast<void*>(pa);
int *pi = static_cast<int*>(pv);

但不能对不同类型的指针进行转换:

int i =0;
int *pa = &i;
char *pc = static_cast<char*>(pa);    //编译报错:invalid static_cast from type 'int*' to type 'char*
long *pl = static_cast<long*>(pa);    //编译报错:invalid static_cast from type 'int*' to type 'long int*'

对于自定义类型,如果类定义转型运算符,那么也可以通过static_cast对类对象进行转型:

#include <iostream>
using namespace std;
 
class A{
public:
    operator int()
    {
        return 1;
    }
    operator char()
    {
        return 'a';
    }
};
 
int main()
{
         A a;
         int ai = static_cast<int>(a);
     char ac = static_cast<char>(a);
     cout<<"ai="<<ai<<endl;    //输出:ai=1
     cout<<"ac="<<ac<<endl;    //输出:ac=a
}

        对于自定义类型的指针,通过static_cast,将派生类指针转换为基类指针是安全的;反之,将基类指针转换为派生类指针是不安全的(建议类指针之间的转换都通过dynamic_cast进行)。

dynamic_cast < type-id > ( expression )

        dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。支持父类指针到子类指针的转换,这种转换时最安全的转换。它是唯一不能用旧风格语法执行的强制类型转换,也是唯一可能有重大运行时代价的强制转换。

const_cast< type-id > ( expression )

        const_cast一般用于强制消除对象的常量性。它是唯一能做到这一点的C++风格的强制转型。这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改。

怎么使用自己制作的静态库或动态库做项目。

reinterpret_cast< type-id > ( expression )

        reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制类型在底层代码以外应该极为罕见。操作 结果只是简单的从一个指针到别的指针的值得二进制拷贝。在类型之间指向的内容不做任何类型的检查和转换。

处理无关类型的转换。不安全,既不在编译器期也不在运行期进行检查,安全性完全由程序员决定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

竹烟淮雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值