温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

C++语言(01)——C到C++的升级

发布时间:2020-07-15 15:10:30 来源:网络 阅读:603 作者:三九感冒灵 栏目:编程语言

1、C++简介

1.1、各种语言的特性

  • c语言:效率高,保留了较多的低级特性(直接操作内存),面向过程
  • C++: 是C语言的是增强版,兼容C语言的所有特性,面向对象,依然保留了部分低级语言的特性
  • java: 是C++语言的剪裁版,完全的面向对象,需要虚拟机运行,所依效率较低
  • C#: 是java语言的竞争者,由微软开发,特性基本与java基本相同
  • D: java和C#的升级,优化了java和C#的运行效率
    C++语言(01)——C到C++的升级
    现代软件产品框架图:
    C++语言(01)——C到C++的升级

    1.2、C++语言的内容

    C++语言是在C语言基础上扩展发展而来,完全兼容C语言。在C语言基础上,C++语言主要包括三大部分:C++对C语言基础语法的扩展,面向对像(继承,封装,多态、异常处理),STL等。

    2、C语言到C++的升级

    共有一下11点,我们一一学习解析:
    1.类型增强
    2.输入与输出
    3.函数重载
    4.操作符重载
    5.默认参数
    6.引用
    7.new/delete
    8.内联函数
    9.类型强转
    10.命名空间
    11.系统string类

    2.1类型增强

    1.变量可以在使用时再定义

    c++更强调语言的实用性,所有的变量可以在使用时再定义,C语言中需要定义在作用域开始的地方。

    2.bool类型

    (1)C语言中是没有bool类型的,要使用int类型来代替。
    (2)C++中有专用的bool类型,占用1个字节的空间,true表示真,编译器内部用1来表示,false表示假,用0表示
    (3)true和false是C++中的关键字。
    (4)C++编译器会将非0值转换为true,将0值转换为false。
    (5)bool类型是C++语言中的基本数据类型,可以定义bool类型的全局变量、常量、指针、数组。

    3.三目运算符

    (1)c语言中也有三目运算符,返回的是变量的值,不能做左值
    (2)C++中的三目运算符可以返回变量本身,既可以做右值,也可以做左值(返回值必须都是变量),如果三目运算符的返回值中有常量,则不能做左值。

#include <stdio.h>
int main(void)
{
    int a = 1,b = 2;
    (a < b ? a : b) = 3;        //正确,返回a或b的引用,可以做左值
    //(a < b ? 1 : b) = 1;      //错误,返回1或b的引用,不可以做左值
    //C++中的三目运算符可以返回变量本身,既可以做右值,也可以做左值(返回值必须都是变量),如果三目运算符的返回值中有常量,则不能做左值

    printf("a = %d, b = %d.\n", a, b);
}

4.枚举

C语言中枚举本质就是整型,枚举变量可以用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化。
在C语言中,枚举的使用:

#include <stdio.h>
enum weekday
{

    monday,
    tuesday,
    wednesday,
    thursday,
    friday,
    saturday,
    sunday
};
int main(int argc, char **argv)
{
    enum weekday day = monday;
    enum weekday a = sunday;
    enum weekday b = 100;

    //weekday c = sunday;错误用法,需要使用enum声明
    printf("%d %d %d\n", day, a, b);

    return 0;
}

在C++语言中枚举的使用:

#include <iostream>
using namespace std;
enum weekday
{
    monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main()
{
    weekday day = sunday;       // 不需要使用enum声明
    enum weekday a = monday;
    // weekday b = 100;     类型不兼容

    cout<<day<<" "<<a<<endl;

    return 0;
}

2. 2.输入与输出

1、cin&cout

cin和cout是C++的标准输入流和输出流,在头文件 iostream 中定义。
流名 含义 隐含设备 流名 含义 隐含设备
cin 标准输入 键盘 cerr 标准错误输出 屏幕
cout 标准输出 屏幕 clog cerr 的缓冲输出 屏幕

#include <iostream>
using namespace std;
int main()
{
    char name[30];
    int age;

    cout<<"pls input name and age:"<<endl;

    cin>>name;
    cin>>age;

    cout<<"your name is: "<<name<<endl;
    cout<<"your age is: "<<age<<endl;

    return 0;
}

2、格式化

A、按进制输出数据类型
B、设置域宽,设置左右对齐及填充字符
C、实型数据的设置

#include <iostream>     // std::cout, std::endl
#include <iomanip>      // std::setw
using namespace std;
int main()
{
    int i = 16;

    // 按进制输出数据类型
    cout<<dec<<i<<endl;
    cout<<hex<<i<<endl;
    cout<<oct<<i<<endl;

    cout << endl;
    cout << endl;

    // 设置域宽,设置左右对齐及填充字符
    cout<<setw(10)<<1234<<endl;
    cout<<setw(10)<<setfill('0')<<1234<<endl;
    cout<<setw(10)<<setfill('0')<<setiosflags(ios::left)<<1234<<endl;
    cout<<setw(10)<<setfill('-')<<setiosflags(ios::right)<<1234<<endl;

    cout << endl;
    cout << endl;

    // 实型数据的设置
    cout<<setw(5)<<'a'<<endl<<setw(5)<<100<<endl
    <<setprecision(2)<<setiosflags(ios::fixed)<<120.00<<endl;

    return 0;
}

2.3函数重载

本节只做简单介绍后续章节会再重点介绍

2.3.1、重载规则

A、函数名相同。
B、参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
C、返回值类型不同则不可以构成重载。

2.3.2、函数重载的匹配规则

A、严格匹配,找到则调用。
B、通过隐式转换寻求一个匹配,找到则调用。
C++允许int到long和double的隐式类型转换,因此遇到这种情型,则会引起二义性,解决方法是在调用时强转类型。

#include <stdio.h>
#include <string.h>
/*
int func(int x)
{
    return x;
}
*/
double func(int x)  //编译报错,前两个函数有歧义,可见返回值函数的返回值不能做为函数重载的依据
{                   //重载的条件:函数名相同,参数的(大小、类型、个数)至少有一个不同
    return x;
}

int func(int a, int b)
{
    return a + b;
}

int func(const char* s)
{
    return strlen(s);
}

int main(int argc, char *argv[])
{
    printf("%d\n", func(3));
    printf("%d\n", func(4, 5));
    printf("%d\n", func("D.T.Software"));

    return 0;
}

2.3.3、函数重载的底层实现

C++利用 name mangling(倾轧)技术,来改名函数名,区分参数不同的同名函数。
实现原理:用 vci f l d表示void char int float long double及其引用。
void func(char a); // func_c(char a)
void func(char a, int b, double c);//func_cid(char a, int b, double c);
name mangling发生在两个阶段,.cpp编译阶段,和.h的声明阶段。只有两个阶段同时进行,才能匹配调用。
C++完全兼容C语言,因此必须完全兼容C的类库。由于.c文件的类库文件中函数名并没有发生name manling行为,而在包含.c文件所对应的.h文件时,.h 文件要发生name manling行为,因而会在编译链接时候发生错误。
C++为了避免上述错误的发生,重载了关键字extern。只需要要避免name manling的函数前,加extern "C"如有多个,则extern "C"{}。
C语言标准库中实际上对C++语言程序引用时做了特殊处理,在C++语言编译器编译时使用extern "C"将C语言的标准库函数排除了命名倾轧。

#ifdef __cplusplus
extern “C”{
#endif
标准库函数
#ifdef __cplusplus
}
#endif

2.4操作符重载:

操作符的重载以函数的方式进行,本质是用特殊形式的函数扩展操作符的功能
操作符的重载不能改变操作符的原生语义

#include <stdio.h>

class Complex 
{
    int a;
    int b;
public:
    Complex(int a = 0, int b = 0)
    {
        this->a = a;
        this->b = b;
    }

    int getA()
    {
        return a;
    }

    int getB()
    {
        return b;
    }

    friend Complex Add(const Complex& p1, const Complex& p2);
};

Complex Add(const Complex& p1, const Complex& p2)

{
    Complex ret;

    ret.a = p1.a + p2.a;
    ret.b = p1.b + p2.b;

    return ret;
}

int main()
{

    Complex c1(1, 2);
    Complex c2(3, 4);
    Complex c3 = Add(c1, c2); // c1 + c2

    printf("c3.a = %d, c3.b = %d\n", c3.getA(), c3.getB());

    return 0;
}

注意:逻辑运算符和逗号表达式重载后不能保持原生语义,原因在于函数参数计算时顺序是不确定的。

#include <iostream>
#include <string>

/**
实际工程中避免重载逻辑操作符,通过重载比较操作符代替逻辑操作符重载或使用成员函数代替逻辑操作符重载
    如果非要重载,使用全局函数进行
**/

using namespace std;

class Test
{
    int mValue;
public:
    Test(int v)
    {
        mValue = v;
    }
    int value() const
    {
        return mValue;
    }
};
//如果非要重载,使用全局函数进行
bool operator && (const Test& l, const Test& r)     //const对象只能调用const成员函数
{
    return l.value() && r.value();
}

bool operator || (const Test& l, const Test& r)
{
    return l.value() || r.value();
}

Test func(Test i)
{
    cout << "Test func(Test i) : i.value() = " << i.value() << endl;

    return i;
}

int main()
{
    Test t0(0);
    Test t1(1);

    //C++通过函数调用扩展操作符的功能,进入函数体前必须完成所有参数的计算,但参数的计算次序是不定的(短路法则失效)
    if( func(t0) && func(t1) )  // operator &&(func(0), func(1))
    {
        cout << "Result is true!" << endl;
    }
    else
    {
        cout << "Result is false!" << endl;
    }

    cout << endl;

    if( func(1) || func(0) )
    {
        cout << "Result is true!" << endl;
    }
    else
    {
        cout << "Result is false!" << endl;
    }

    return 0;
}

2.5默认参数&占位参数:

C++中引入了默认参数的概念,可以从左向右依次提供默认参数

#include <stdio.h>

int add(int x, int y = 0, int z = 0);   //设计函数时,参数的默认值必须从右向左提供

int main(int argc, char *argv[])
{
    printf("%d\n", add(1));
    printf("%d\n", add(1, 2));
    printf("%d\n", add(1, 2, 3));

    return 0;
}

int add(int x, int y, int z)
{
    return x + y + z;
}

一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法确认是重载还是默认参数。
占位参数只有函数的参数类型声明,而没有参数名声明,为了兼容C语言的不规范写法

#include <stdio.h>

int func(int x, int);
// int func(int x, int = 0);    //解决方法是结合默认参数和占位参数

int main(int argc, char *argv[])
{
    printf("%d\n", func(1));    //这样可以使用不同的方法调用
    printf("%d\n", func(2, 3));

    return 0;
}

int func(int x, int)
{
    return x;
}

2.6引用:

引用是一个变量的别名,本质是一个指针常量。

int a,b;
int &r = a;
int &r = b; //错误,不可更改原有的引用关系
r = b;//正确,赋值
float &rr = b; //错误,引用类型不匹配
cout<<&a<<&r<<endl; //变量与引用具有相同的地址。
int &ra = r; //可对引用更次引用,表示 a 变量有两个别名,分别是 r 和 ra

//引用的应用
void swap(int &a, int &b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}
void swap(char * &a, char * &b)
{
        char *t = a;
    a = b;
    b = t;
}

//引用的提高
//A、可以定义指针的引用,但不能定义引用的引用。
int a;
int* p = &a;
int*& rp = p; // ok
int& r = a;
int&& rr = r; // error

//B、可以定义指针的指针(二级指针),但不能定义引用的指针。
int a;
int* p = &a;
int** pp = &p; // ok
int& r = a;
int&* pr = &r; // error
//C、可以定义指针数组,但不能定义引用数组,可以定义数组引用。
int a, b, c;
int* parr[] = {&a, &b, &c}; // ok
int& rarr[] = {a, b, c}; // error
int arr[] = {1, 2, 3};
int (&rarr)[3] = arr; // ok 的

//E、常引用
const对象的引用必须是const的,将普通引用绑定到const对象是不合法的。const引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化,是const引用与普通引用最大的区别。 const int &a=2;是合法的。 double x=3.14; constint &b=a;也是合法的。
非const引用只能绑定到与该引用同类型的对象。
总结:
A、引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
B、声明的时候必须初始化,一经声明,不可变更。
C、可对引用再次引用。多次引用的结果,是某一变量具有多个别名。
D、&符号前有数据类型时,是引用。其它皆为取地址。
E、不可定义引用的指针,不可定义引用数组(数组内部数据元素类型必须相同)

2.7new/delete:

C++中引入了新的内存分配机制
(1)C语言中通过malloc和free来申请和释放内存空间
(2)C++中使用new和delete来来申请和释放内存空间
(3)new和malloc的区别
1、new是C++中的关键字而malloc是C语言中的一个库函数
2、new以具体的类型为单位进行内存的分配,而malloc是以字节为单位进行内存分配
3、new在申请单个类型变量时可以进行初始化,而malloc不具有初始化的特新
(4)new的使用

//1、//申请type类型的空间
type *pointer = new type    
delete pointer
//2、//为type类型的数组申请空间,数组中有N个元素
type *pointer = new type[N] 
delete[] pointer    //此处的[]不能丢
//3、//申请type类型的空间,并赋值为1
type *pointer = new type(1) 
delete pointer

使用实例:

#include <stdio.h>

int main()
{
    int* p = new int;   //从字面来理解,为新类型int分配空间

    *p = 5;
    *p = *p + 10;

    printf("p = %p\n", p);
    printf("*p = %d\n", *p);

    delete p;

    p = new int[10];        //堆内存申明是一般所给的内存会比实际申请的要多一些
                            //所依此处至少分配了40字节的空间

    for(int i=0; i<10; i++)
    {
        p[i] = i + 1;

        printf("p[%d] = %d\n", i, p[i]);
    }

    delete[] p;         //此处的[]不可以丢

    return 0;
}

2.8内联函数:

2.8.1、函数与宏的区别
(1)普通函数由编译器处理
优势:编译器会做静态的参数类型检查(返回值,参数)
缺点:在于有调用开销(压栈、跳转、返回)
(2)带参宏由预处理器处理
优势:直接字符替换,没有调用开销
缺点:没有静态参数类型检查,不如函数直观,易出错
2.8.2内联函数
优点:避免调用时的额外开销(入栈与出栈操作)
缺点:内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间。
本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
适用场景:函数体很“小”,且被“频繁”调用。
2.8.3内联函数
(1)结合了宏定义和函数的优势,直接将函数体插入函数调用的地方,没有调用开销,使用inline关键字来申明。
(2)C++编译器不一定会满足函数的内联请求,类似于register关键字
(3)在扩展C++编译器中提供了强制内联的关键字,可以实现内联,此外还可以通过对编译器进行配置,从而强制内联。
(4)g++中使用:attribute((always_inline))来强制内联
vc10.0中使用:__forceinline实现
(5)老版编译器(标准c)中不支持强制内联

2.9类型强转:

1、静态类型转换,静态类型转换是在编译期内即可决定其类型的转换。
2、动态类型转换,用于多态中的父子类之间的强制转化
2、常量类型转换,目标类类型只能是指针或引用,const_cast将转换掉表达式的const属性
4、重解释类型转换,为数据的二进制形式重新解释,但是不改变其值。

#include <stdio.h>

void static_cast_demo()
{
    int i = 0x12345;
    char c = 'c';
    int* pi = &i;
    char* pc = &c;

    c = static_cast<char>(i);
    //pc = static_cast<char*>(pi);      //error
}

void const_cast_demo()
{
    const int& j = 1;       //定义了一个只读变量j
    int& k = const_cast<int&>(j);

    const int x = 2;        ////定义了一个常量x
    int& y = const_cast<int&>(x);   //当&/extern作用于一个常量时,c++也会像C语言中一样为其分配内存空间

    //int z = const_cast<int>(x);       //error

    k = 5;

    printf("k = %d\n", k);      // 5
    printf("j = %d\n", j);      // 5

    y = 8;

    printf("x = %d\n", x);      // 2, x是常量,使用时从字符表取出
    printf("y = %d\n", y);      // 8
    printf("&x = %p\n", &x);    //y是x的引用(别名),共用同一内存空间
    printf("&y = %p\n", &y);
                                //&x = 0xbf8872dc
                                //&y = 0xbf8872dc
}

void reinterpret_cast_demo()
{
    int i = 0;
    char c = 'c';
    int* pi = &i;
    char* pc = &c;

    pc = reinterpret_cast<char*>(pi);
    pi = reinterpret_cast<int*>(pc);
    pi = reinterpret_cast<int*>(i);
    //c = reinterpret_cast<char>(i);        // error
}

void dynamic_cast_demo()
{
    int i = 0;
    int* pi = &i;
    //char* pc = dynamic_cast<char*>(pi);   //error  
}

int main()
{
    static_cast_demo();
    const_cast_demo();
    reinterpret_cast_demo();
    dynamic_cast_demo();

    return 0;
}

//C++ 内存申请失败会抛出异常

try{
    int *p = new int[10];
}
catch(const std::bad_alloc e)
{
    return -1;
}
//C++ 内存申请失败不抛出异常版本
int *q = new (std::nothrow)int[10];
if(q == NULL)
    return -1;

正是由于C++的内存申请会抛出异常,所以在面对自定义类型对象构造时,一定要确保异常安全。

2.10命名空间:

解决C语言中的全局变量命名冲突的问题,可以嵌套使用

#include <stdio.h>
namespace First     //从全局空间中划分出一块命名为First,类似于划分地域
{
    int i = 0;
}
namespace Second
{
    int i = 1;

    namespace Internal
    {
        struct P    //c++中此处的P即就是一个结构体类型,等价于C语言中的struct P
        {
            int x;
            int y;
        };
    }
}
int main()
{
    using namespace First;          //我们要在main函数中使用First命名空间中的一切
    using Second::Internal::P;      //我们要在main函数中使用Second命名空间中的Internal空间中的P这个结构体

    printf("First::i = %d\n", i);
    printf("Second::i = %d\n", Second::i);

    P p = {2, 3};       //C++ 里对struct关键字进行加强,可以直接定义结构体 

    printf("p.x = %d\n", p.x);
    printf("p.y = %d\n", p.y);

    return 0;
}

2.11系统string类

本质上是C++中的自定义类型,重载了数组操作符,支持C语言中但字节访问的特定。
除了使用字符数组来处理字符串以外,C++引入了字符串类型。可以定义字符串变量。

2.11.1、定义和初始化

string str;
str = "china";
string str2 = " is great ";
string str3 = str2;

2.11.2、类型大小

cout&lt;&lt;sizeof(string)&lt;&lt;" "&lt;&lt;str.max_size()&lt;&lt;str.size()&lt;&lt;endl;

2.11.3、运算

//A、赋值
string str3 = str2;
//B、加法
string combine = str + str2;
//C、关系
string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;

2.11.4、string类型数组

string数组是高效的,如果用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。如果用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,但是操作麻烦。用 string 数组存储,字符串数组的话,效率即高又灵活。

string sArray[10] = {
"0",
"1",
"22",
"333",
"4444",
"55555",
"666666",
"7777777",
"88888888",
"999999999",
};
for(int i=0; i<10; i++)
{
cout<<sArray[i]<<endl;
}

2.11.5、string类的成员函数

int capacity()const;  //返回当前容量(即string中不必增加内存即可存放的元素个数)
int max_size()const;    //返回string对象中可存放的最大字符串的长度
int size()const;        //返回当前字符串的大小
int length()const;       //返回当前字符串的长度
bool empty()const;        //当前字符串是否为空
void resize(int len,char c);//把字符串当前大小置为len,并用字符c填充不足的部分

12.C语言开发人员的建议

1、在 C++中几乎不需要用宏,用const或enum定义明显的常量,用inline避免函数调用的额外开销,用模板去刻画一族函数或类型,用namespace去避免命名冲突。
2、不要在你需要变量之前去声明,以保证你能立即对它进行初始化。
3、不要用malloc,new运算会做的更好
4、避免使用 void*、指针算术、联合和强制,大多数情况下,强制都是设计错误的指示器。
5、尽量少用数组和C风格的字符串,标准库中的string和vector可以简化程序
6、更加重要的是,试着将程序考虑为一组由类和对象表示的相互作用的概念,而不是一堆数据结构和一些可以拨弄的二进制。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI