C++学习之路 - C++相对于C的扩展

总概述

书籍

当字典

《C++ Primer》《C++程序设计原理与实践》

跨平台

路程

常用语法

知识点

案例

面向对象

兼容C语言代码效率

兼顾Java数据抽象

标准C++一般基于C++98

C++11也在逐渐成为大众标准

C++概述

历史背景

江湖地位

  • 编程语言现在大约100种

  • 热度排名

    • tiobe编程语言排行

    • JAVA范围广、可移植、门槛低

    • C++门槛高、薪资高

      • 面向对象

        • 介于C和Java

          • 代码效率高

            • 具备C语言和Java的优点
    • C语言市场份额大但相比于Java场景范围不一样

      • 相比于Java(封装高)代码执行效率高但代码庞大
      • 底层
    • Python是脚本语言,特点是人工智能的应用,语法特性和C很像,但是更简单

C++之父

姓名

  • Bjarne Stoustrup(1950--)

贡献(1979)

  • 预处理程序

    • Cpre

      • 为C语言增加了类的机制

贡献(1983)

  • C with Class

    • 后来称为C++

贡献(1985)

  • 第一个C++编译器

    • CFront 1.0
  • 第一本C++语法书

    • 《The C++ Programming Language》

C++ 发展过程

1987,GNU C++

1990,Borland C++

1992,Microsoft C++(VC,VS)

1998,ISO C++98(国际标准)(主要)

2003,ISO C++03

  • 优化完善,实际上微乎其微

2011,,ISO C++11

2014,ISO C++14

2017,ISO C++17

*2020,ISO C++20

应用领域

游戏开发

  • 主流开发方向
  • 性能和效率高
  • 适合大型开发

科学计算

网络和分布式应用

操作系统和设备驱动

其它...

C和C++

相同点

  • 1.编译型语言,都同样采用静态编译
  • 2.强类型语言,但C++更强

不同点

  • 1.C++去除了C中不好的特性

  • 2.增加了C语言中没有的好的语法特性

    • 全面支持面向对象
    • 比C语言更适合大型软件开发

第一个C++程序

#include<iostream>
int main(void){
std::cout << "hello world!" << std::endl;
return 0;

编译方式

  • gcc xx.cpp

    • -lstdc++
  • g++ xx.cpp

    • 更方便,推荐!

文件扩展名

  • .cpp(推荐)
  • .cc
  • .C(大写)
  • .cxx

头文件

  • include

    • 在C++中,和I/O相关的类型、对象、函数都在头文件中
  • C++绝大多数头文件没有".h"后缀

  • 在C++开发中依然可以使用标准C库的头文件,另外,在C++中还提供一套不带".h"替换版本

    • include<stdio.h> ==> #include

    • include<stdlib.h> ==> #include

    • include<string.h> ==> #include

  • c++头文件位置

    • /usr/include/c++/...

C++标准输入和输出

  • cin对象表示标准输入//类似scanf

    • 从键盘读取一个int数据

      • C语言

        • int num = 0;
          scanf("%d",&num);
      • C++

        • cin >> num;
          /和C语言">>"按位右移无关,被称为输入(提取)操作符/
    • 从键盘同时读取一个int和一个double数据

      • C语言

        • int i =0,double d = 0.0;
          scanf("%d%lf",&i,&d);
      • C++

        • cin >> i >>d;

C++标准输出

  • 类似于C语言的printf

    • 打印输出int类型数据

      • C语言

        • int num = 123;
          printf("%d\n",num);
      • C++

        • cout << num << endl;
    • 同时打印输出int和一个double
      int i=100,double d = 1.23;

      • C语言

        • printf("%d,%lf\n",i,d);
      • C++

        • cout << i << ',' << d <<endl;

面向对象

C

  • 逐一刻画细节

    • 一个步骤出现问题会牵一发动全身

C++

  • 先对整体描述,后刻画细节

C++的"<<"">>"

<<

  • 被称为输出(插入)操作符

>>

  • 输入操作符

编译步骤

1.预处理

2.编译,检查语法

3.汇编

4.链接

名字空间(命名空间)

背景

大项目变量重名

分配变量作用范围

意义(作用)

在不同空间,相同的名字在连接时相互不可见,避免冲突

划分逻辑单元

语法和定义

namespace 名字空间{

名字空间成员1;
名字空间成员2;
...

}

  • 注意:
    名字空间的成员可以是全局函数、可以是全局变量、自定义类型、名字空间

表达大的作用域范围

成员的使用

通过作用域限定操作符

  • ::(类似于中文“的”)

    • 名字空间名 :: 要访问的成员;

示例

  • 1 #include
    2
    3 namespace ns1{
    4 void func(void){
    5 std::cout << "ns1的func" << std::endl;
    6 }
    7 }
    8 namespace ns2{
    9 void func(void){
    10 std::cout << "ns2的func" << std::endl;
    11 }
    12 }
    13
    14 int main(void){
    15 //func();//直接访问报错
    16 ns2::func();//可以通过作用域限定访问
    17 return 0;
    18 }

  • 简化方法

    • 名字空间指令

      • using namespace 名字空间名;

        • 注:在该条指令以后的代码中,指定名字空间的成员都可见,可以直接访问,省略"名字空间名::"

          • eg:
            using namespace std;
            cout << a;//OK
    • 名字空间声明

      • using 名字空间名::名字空间成员;

        • 注:
          将名字空间中特定的一个成员引入到当前作用域,在该作用域访问这个成员就如同访问自己的局部成员一样,可以直接访问,省略"名字空间名::"

全局作用域和匿名名字空间

没有放在任何名字空间的成员属于全局作用域,可以直接访问,但如果和局部作用域的成员名字一样,局部优先;这时如果还希望访问全局作用域的成员,可以通过"::xx"显式访问

定义名字空间时可以没有名字,即为匿名名字空间,匿名名字空间中的成员和全局作用域类似,可以直接访问,也可以通过":::xx:"显式的访问,唯一的区别是匿名名字空间的成员被局限在当前文件中使用

*名字空间的嵌套和合并

名字空间嵌套

  • eg:
    namespace ns1{
    int num = 100;
    namespace ns2{
    int num = 200;
    namespace ns3{
    int num = 300;
    }
    }
    }

    • cout << ns1::num <<endl;//100
    • cout <<ns1::ns2::num << endl;//200
    • cout << ns1::ns2::ns3::num << endl;//300

名字空间合并

  • 两个同名名字空间会自动合并成一个

    • namespace ns1{//1.cpp
      void func1(void){...}
      }
      namespace ns1{//2.cpp
      void func2(void){...}
      }

      • 空间名一样->合并
        成员名一样->歧义报错

第三章~第十一章为C++相对于C的扩展

C++的结构体、联合体、枚举

和C语言完全兼容

介绍和C语言相比一点点 的语法扩展

结构体

当定义结构体变量时可以

直接省略struct 关键字

  • C语言

    • struct HttpRequest{...};

      • struct HttpRequest req1;
  • C++

    • struct HttpRequest{...};

      • HttpRequest req2;
  • 相比于C语言略微精炼

在C++结构体内部可以直接定义函数,称为成员函数(方法),在成员函数内部可以直接访问结构体的其它成员.

枚举

用的多,一般大项目使用广泛

常量集合或者列表

意义:提高代码的可读性

  • enum STATE{SLEEP=0,RUN=1,STOP=2};

C++枚举和C语言枚举的区别

  • 定义枚举变量时可以省略enum关键字

  • C++中的枚举被看做是独立数据类型,不能把枚举直接当做整形数来使用

    • enum STATE{SLEEP=0,RUN=1,STOP=2};
      /enum/ STATE s;
      s = RUN;//OK 和C语言一样
      s = 1;//C语言OK //C++报错

*联合体

存储于相同的地址

当定义联合体变量时可以省略union关键字

C++中支持匿名联合

C++的字符串(重点)

回顾C语言字符串

字面值常量字符串:"..."

字符指针:char*

字符数组:char[]

C++兼容C风格字符串,同时增加了string类型,专门表示字符串

更安全更方便

定义

string s1;//定义空字符串

暂时可以将string 理解为结构体类型(不准确)

string不是关键字

string s2 = "xx";//定义同时初始化

字符串拷贝

string s1 = "youchengwei";

string s2 = "mindasheng";
s1 = s2;//将s2拷贝给s1
cout << s1 << endl;//“mingdasheng”

  • 简单、安全
    无越界,在堆区完成

字符串连接

string s1 = "hello";

string s2 = " world";
string s3 = s1 + s2;
cout << s3 << endl;//"hello world"

  • 无需担心内存问题,在堆区完成

string 核心

struct string{

char *m_str;//指向堆区

}

字符串比较

if(s1==s2)

if(s1 != s2)

比较大小是比较首字母ASCII码值,而非长短

随机访问

字符串数组下标访问

  • string s = "minwei";

    • 将"m""w"改成大写

      • s[0] = 'M';
        s[3] = 'W';
        cout << s << endl;MinWei

其它函数

size() / length();//获取字符串长度

  • eg:
    string str = "youchengwei";
    str.size();//求字符串长度 11
    str.length();//11 和size等价

c_str();//返回字符串的起始地址(const char*)

  • string s1 = "hello";//C++风格
    const char* s2 = "world";//C风格
    s1 = s2;//OK C++兼容C
    s2 = s1;//error C不兼容C++
    s2 = s1.c_str();
  • 作用:类型转换,兼容C风格

段错误

一般是SIGSEGV

一般是对内存的某些数据区域的访问出现了问题,一般为非法访问内存

内存泄露

堆区内存分配了,没有及时释放,浪费了内存资源

进程运行起来后

内存的情况

栈区(高地址)

自动管理自动回收

  • 非静态局部变量
  • 函数形参

堆区,分配了要及时释放

  • 动态内存

    • malloc
    • ...

全局区(整个进程的生命周期)

全局变量在这个区

  • 数据区(初始化)
  • BSS区(未初始化)

代码段(只读)

  • 字面值常量
  • 代码指令本身
  • ...

C语言字符串比较

strcmp()

if(strcmp(s1,s2))

  • s1,s2相等,返回0

类是结构体的扩展

里面包含了很多成员函数

语义差异

string

  • 字符容器

    • size和length互相调用

练习1

题目:使用string表示字符串,从键盘读取一个字符串并且统计里面包含字符"Y/y"个数

提示:

string str;
cin >> str;//从键盘读取一个字符串

练习2

题目:从键盘读取字符串,实现字符串的反转(逆置)

布尔类型(bool)

算C++基本数据类型

专门表示逻辑值,逻辑真用true表示,逻辑假用false表示.

大小

布尔类型在内存占一个字节

  • 1表示true
  • 0表示false

接受表达式结果

bool类型变量可以接受任意表达式的结果,其值非0则为true,为0则为false

空指针为false,非空指针为true

操作符别名

背景

某些西方欧洲国家,键盘上的字符比较多但是没有 && || 等等

意义

别名替换

代换

&& <==> and

|| <==> or

{ <==> <%

} <==> %>

....

函数

函数重载

(overload)

背景

  • 函数功能类似

    • eg:
      strcpy(s1,s2);
      strncpy(s1,s2,n);

eg:

实现图形库的绘图函数

  • C语言方式

    • //画矩形
      void drawRect(int x,int y, int w,int h){
      ...
      }
  • C++方式

    • //画图形
      void draw(int x,int y,int w,int h){
      ...
      }

定义

  • 在相同的作用域定义同名的函数,但是参数必须有所区分,这样函数构成重载关系

    • 注:
      函数重载和返回类型无关

条件

  • 相同作用域
  • 同名函数
  • 参数有所区分(参数个数和类型)

调用重载关系函数时,一般是由编译器根据实参和形参的匹配程度,自动选择最优的重载版本.

  • 当前g++匹配的一般规则:
    完全匹配->常量转换->升级转换->降级转换->省略号匹配

原理

  • C++的编译器在编译函数时会进行换名,即将参数表的类型信息整合到新的函数名中,因为重载关系的函数参数表有所区分,换出的新的函数名也一定有所区分,解决了函数重载和名字冲突的矛盾.

    • 笔试题例子

      • Q:C++中extern “C”作用?
      • A:显式告诉编译器按照C语言方式编译该函数(不换名,方便C程序直接调用,但和重载矛盾,声明的函数无法重载).

函数的缺省参数

(默认实参[重点])

可以为函数参数指定缺省值,调用该函数时,如果不给实参,就取缺省值作为默认实参

  • eg:
    void func(int i,int j=0/缺省参数/){}

靠右原则

  • 如果函数的某个参数带有缺省值,那么该参数右侧的所有参数都必须带有缺省值

缺省参数只能在定义和声明之一写一次

  • 但是绝大多数缺省参数是只写在声明部分,定义部分不写
  • 定义部分写法:
    void func(int a,int b,int c/=10/){}

函数的哑元参数

定义

  • 在定义函数时,只有类型而没有变量名的形参被称为哑元
  • void func(int /哑元/)

使用场景

  • eg:
    在操作符重载函数中,区分前后++、--

  • eg:
    为了兼容旧的代码

    • 标识该参数是无用的

内联函数(inline)

定义

  • 使用inline关键字修饰的函数,即为内联函数,编译器将会尝试进行内联优化,可以避免函数调用开销,提高代码执行效率.

使用例子

  • inline void func(void)

特点

  • 空间换时间

适合场景

  • 多次调用小而简单的函数

不适场景

  • 调用次数极少或大而复杂的函数

  • 递归函数不能内联优化

    • 递归函数的递归次数在编译器的编译期间无法确定
  • 虚函数

  • 内联只是一种建议,而不是一种强制的语法要求,一个函数能否内联优化主要取决于编译器,有些函数不加inline修饰也会默认处理为内联优化,有些函数即便加了inline修饰也会被编译器忽略.

动态内存管理

回顾C语言的

动态内存管理

分配

  • malloc(使用居多)
  • calloc
  • realloc
  • brk
  • sbrk
  • mmap

释放

  • free

C++动态内存管理

分配

  • new
  • new[]

释放

  • delete

    1 #include
    2 using namespace std;
    3
    4 int main(void){
    5 int *p1;
    6 //delete p1;//delete野指针,危险!!!
    7
    8 int *p2 = NULL;
    9 delete p2;//delete空指针,安全,但是无意义
    10
    11 int *p3 = new int;
    12 delete p3;
    13 delete p3;//不能多次delete同一个地址,结果不确定
    14
    15 return 0;
    16 }

  • delete[]

只分配不释放

内存泄露

区别和联系

联系

  • 功能差不多

区别

  • 语法层面

    • malloc是函数

      • 无初始化
    • new和delete是操作符

      • 有初始化
  • 对对象的操作

引用

(Reference)

定义

引用即别名,引用就是某个变量别名,对引用操作和对变量本身完全相同

语法

类型 & 引用名 = 变量名

eg:

int a = 10;
int & b = a;//b就是a的别名,等号是初始化

  • 引用必须在定义同时初始化,而且在初始化以后所绑定的目标变量不能再做修改
  • 引用的类型和绑定目标变量类型要一致

常引用

定义

  • 定义引用时,可以多加const修饰,即为常引用,不能通过常引用修改目标变量(只读).

const 类型 & 引用名 = 变量名;

类型 const & 引用名 = 变量名;

  • 两个方法等价
  • int a =10;
    const int &b = a;//b就是a的常引用
    cout << b << endl;//10
    b++;//error

普通引用也可称为左值引用,只能引用左值;而常引用也称为万能引用,既可以引用左值也可以引用右值.

引用型函数参数

可以通过形参直接修改实参的值

避免函数调用时传参的开销,提高代码效率

引用型参数有可能意外修改实参的值,如果不希望修改实参,可以将形参声明为常引用,提高效率的同时还可以接受常量型的实参.

引用型函数返回值

背景

  • 函数的返回是返回的和return值相同的临时变量,内存会有开销

可以将函数的返回类型声明为引用,这时函数的返回结果就是return后面数据的别名,可以避免返回值带来的开销,提高代码执行效率.

如果函数的返回类型是左值引用,那么函数调用表达式的结果也将是左值

  • int &func(void){
    ...
    return num;
    }

func() = 100;//OK

注:

不要从函数中返回局部变量的引用,因为所引用的内存会在函数返回后被释放,使用非常危险!可以在函数中返回成员变量、静态变量、全局变量的引用.

重难点

关于左值和右值

左值

  • 可以放在赋值表达式左侧,可以被修改

    • 如普通变量
  • 也可以放右边

右值

  • 只能放在赋值表达式右侧,不可以被修改

    • 如字面值常量

左值和右值是对立的

练习

测试下面表达式结果,哪些是左值哪些是右值

  • int a,b;
    a+b;//右值
    a+=b;//左值
    ++a;//左值
    a++;//右值

总结关于引用和指针的区别和联系

  • 从C语言的角度看待引用的本质,可以认为引用就是通过指针实现的,但是在C++开发中推荐使用引用,而不推荐指针.

    • int i = 100;
      int* const pi = &i;
      int& ri = i;

      • *pi <=等价=> ri
  • 区别

    • 指针可以不做初始化,其目标可以在初始化后随意改变(指针常量除外).

      • int a = 10,b = 20;
        int *p;//ok
        p = &a;
        p = &b;
    • 引用必须初始化,且一旦初始化,所引用的目标不能再改变

      • int& r;//error
        int& r = a;
        r = b;//ok,但不是修改引用目标,仅是赋值运算.
    • 可以定义指针的指针(二级指针),但不能定义引用的指针

      • int a =10;
        int p = &a;
        int
        *pp = &p;//二级指针

      • int& r =a;
        int& *pr = &r;//error,引用的指针

        • 引用在C++没有自己独立的地址
      • int* pr = &r;//ok,仅是普通指针和int *p = &a;等价

    • 可以定义指针的引用(指针变量别名),但是不能定义引用的引用

      • int a = 100;
        int* p = &a;
        int* & rp = p;//ok,指针的引用
      • int& r = a;
        int& & rr = r;//error,引用用的引用
        int& rr = r;//ok,但仅是一个普通引用
    • 可以定义指针数组,但是不能定义引用数组

      • int i = 10,j = 20,k = 30;
        int* parr[3] = {i,j,k};//OK,指针数组
        int& rarr[3] = {i,j,k};//error,不存在此写法
    • 可以定义数组引用(数组别名)

      • int i = 10,j = 20,k = 30;
        int arr[3] = {i,j,k};
        int (&rarr)[3] = arr;//ok
    • 和函数指针类似,也可以定义函数引用(函数别名)

      • void func(int i){...}
        int main(void){
        void (*pf)(int) = func;//函数指针
        void (&rf)(int) = func;//函数引用
        pf(100);
        rf(100);
        }

类型转换

背景

a = b;

  • 保证a和b数据类型相同

隐式类型转换

场景

  • char c = 'A';
    int i = c;//隐式
  • void func(int i){}
    func(c);//隐式
  • int func(void){
    char c = 'A';
    return c;//隐式
    }

显式类型转换

背景

  • cout << &data << endl;//1 ...

关于强制类型转换,兼容C强制类型转换

  • char c = 'A';
    int i = (int)c;//C风格
    int i = int(c);//C++风格

C++扩展了四种操作符形式显式转换

  • 静态类型转换

    • static_cast

    • 语法

      • 目标变量 = static_cast<目标类型>(源类型变量)
    • 场景

      • 将void*转换为其它类型的指针
  • 动态类型转换

    • dynamic_cast//后面提

    • 语法

      • 目标变量 = dynamic_cast<目标类型>(源类型变量)
  • 去常类型转换

    • const_cast

    • 语法

      • 目标变量 = const_cast<目标类型>(源类型变量)
    • 场景

      • 去掉指针或引用const属性
      • int i = 0;//栈区可读可写
        const int i = 0;//栈区可读可写
  • 重解释类型转换

    • reinterpret_cast

    • 语法

      • 目标变量 = reinterpret_cast<目标类型>(源类型变量)
    • 场景

      • 指针和整型数进行显式转换.

        • eg:
          已知物理内存地址0x12345678,向该地址存放一个整型数100?

          • int *paddr = 0x12345678;
            *paddr = 100;//error
          • int paddr = reinterprect_cast<int>(0x12345678);
            *paddr = 100;

volatile

是标准C语言的关键字

被此修饰的变量是易变的,告诉编译器每次使用该变量时,都要小心从内存中读取,而不是取寄存器的副本,防止编译器优化引发的错误结果.

来自C++

社区建议

慎用宏

可以使用const、enum、inline替换

弊端

  • 都是全局的

    • 替换例子

      • define PAI 3.14

        • ==>

          • const double PAI = 3.14;
      • define SLEEP 0

        define RUN 1

        define STOP 2

        • ==>

          • enum STATE{SLEEP,RUN,STOP};
      • define Max(a,b) ((a)>(b)?(a):(b))

        • inline int Max(int a,int b){
          return a>b?a:b;
          }

变量随用随声明,同时初始化

尽量使用new/delete替换malloc/free

少用void*、指针计算、联合体、强制转换

尽量使用string表示字符串,少用C风格的char*/char[]

posted @ 2022-03-05 08:51  盾牌座UY  阅读(173)  评论(0)    收藏  举报