C++(C++基础/同C的差异分析)

#include <iostream>
using namespace std;
​
int main(int argc,const char* argv[])
{
    cout << "Hello World!" << endl;
    return 0;
}
文件扩展名:

cpp、C、cxx

头文件:

C++语言的标准库文件,文件名的末尾不带.h,iostream用于标准输入输出头文件,C语言的相关头文件还可以继续使用。

为了统一命名风格,C++为C语言重定义了不带.h标准库头文件,例如:stdio.h重定义了cstdio。

自定义的头文件,还可以继续以.h结尾。

编译器:

g++,相关参数的使用方法与gcc一样。

输入、输出:

cout、cin是用于输入、输出的标准库类对象。

cout << 要输出的数据 << endl,多个数据用<<隔开。

cin >> 变量名,多个数据使用>>隔开。

cout和cin可以自动识别数据类型,但输入输出复杂格式的数据时,没有printf和scanf好用。

printf和scanf还可以继续使用,但需要包含相关的头文件。

名字空间:

为了避免命名冲突,C++中引入了一项命名空间的管理技术 名字空间,后续再讲解。

注意:C++基本上完全兼容C语言的所有内容。

练习:

输入n个整数,计算出它们的最大值、最小值、平均值。

#include <iostream>
using namespace std;
​
int main(int argc,const char* argv[])
{
    int n;
    cin >> n;
​
    int val, max=0x80000000, min=0x7fffffff, sum=0;
    for(int i=0; i<n; i++)
    {
        cin >> val;
​
        sum += val;
        if(val > max)
            max = val;
        if(val < min)
            min = val;
    }   
    cout << max << " " << min << " " << sum / n << endl;
    return 0;
}

二、基本数据类型的不同

bool类型:

在C++中,bool就是一种真正的基本数据类型,不需要包含stdbool.h头文件了,bool、true、false是C++中的关键字。

可以给bool类型的变量赋值整数,但它会自动转换成0或1。

#include <iostream>
using namespace std;
​
int main(int argc,const char* argv[])
{
    bool flag;
    cout << sizeof(bool) << " " << sizeof(true) << " " << sizeof(false) << endl;
    
    flag = 4;
    cout << flag << endl; // 输出的1,会把整数自动转换成0|1。
    return 0;
}

void类型的指针:

在C语言void类型的指针可以与其它类型的指针自动转换(通用指针),而在C++中:

其它类型指针 可以自动转换成 void*,之所以保留是因为C标准库、操作系统、第三方库中有大量的函数的参数使用了void*作为参数,如果该功能不保留,这类函数就无法再正常调用。

void* 不能再自动转换成 其它类型指针,为了安全C++语言对类型检查比C语言要严格很多。

#include <iostream>
#include <cstdlib>
using namespace std;
​
int main(int argc,const char* argv[])
{
    int* p = (int*)malloc(40);
    return 0;
}
字符串:

在C语言中使用char类型的数组或char*指针指向的内存来存储字符串,使用string.h中的函数操作字符串,但在C++中使用string类型字符串变量,使用相关的运算符操作字符串。

#include <iostream>
using namespace std;
​
int main()
{
    // 定义字符串对象
    string str; 
    // 输入字符串,不用关心存储空间是否够用,会自动扩展
    cin >> str;
    // 输出字符串
    cout << str << endl;
​
    // 给字符串赋值,strcpy
    str = "hello";
    // 追加字符串,strcat
    str += "world";
    // 计算字符串长度,strlen
    cout << str.size() << endl;
    // 比较字符串,== != > < >= <= ,strcmp
    cout << (str == "xixi") << endl;
}

注意:string类型的底层,依然是使用char类型的指针、数组实现的,并不是一种全新的类型,而是对char字符串的封装。

三、结构、联合、枚举的区别

结构、联合的区别:

1、在C++中定义结构、联合对象时,struct、union关键字可以省略(也就不需要使用typedef进行类型重定义)。

2、在C++中结构、联合的内部,可以有成员函数,使用结构、联合对象加.或->调用,成员函数的内部可以直接使用成员变量,成员函数可以自动区别对象的成员变量。

3、在C++中结构、联合中可以对成员变量、成员函数进行访问权限的管理:

private 私有的成员

public 公开的成员

protected 受保护的成员

4、在C++中创建、销毁结构、联合对象时,会自动调用构造函数(以结构名命名)、析构函数(~结构名命名)。

#include <iostream>
using namespace std;
​
struct Student
{
    int id;
    char name[20];
    short age;
    void show(void)
    {
        cout << id << " " << name << " " << age << endl;
    }
    Student(void)
    {
        cout << "我是构造函数" << endl;
    }
    ~Student(void)
    {
        cout << "我是析构函数" << endl;
    }
};
​
union Data
{
    char ch;
    int num;
};
​
int main(int argc,const char* argv[])
{
    /*
    Student stu1 = {10010,"hehe",28};
    Student stu2 = {10011,"xixi",30};
    stu1.show();
    stu2.show();
    */
    Student stu;
    Data d;
​
    return 0;
}
枚举:

1、定义枚举变量时,enum关键字可以省略。

2、C++中的枚举不再是int类型模拟的,整数不能给枚举变量赋值。

#include <iostream>
using namespace std;
​
enum DirectionKey
{
    Up,Down,Right,Left
};
​
int main(int argc,const char* argv[])
{
    DirectionKey key;
    // key = 1234;
    key = Down;
    cout << key << endl;
    return 0;
}

四、函数的区别

1、函数重载:

1、C++中的函数重名,我们把这项技术叫函数重载。

2、重载的函数,参数列表必须不同,即参数的个、类型不同。

3、调用函数时会根据实参的数据类型自动选择调用那个函数,如果与实参相符的函数没有定义,则可以对实参进行自动类型转换调用相关函数,如果实参进行自动类型转换后有多个选则会导致调用冲突。

#include <iostream>
using namespace std;
​
void func(long num)
{
    cout << "func long类型参数" << endl;
}
​
void func(short num)
{
    cout << "func short类型参数" << endl;
}
​
int main(int argc,const char* argv[])
{
    func(1234);
    return 0;
}
​

4、如果函数的参数是指针或引用,指针变量是否使用const修饰会影响函数重载。

void func(int* p)
{
    cout << "func int* p" << endl;
}
​
void func(const int* p)
{
    cout << "func const int* p" << endl;
}
​
int main(int argc,const char* argv[])
{
    int num1;
    func(&num1); // void func(int* p)
    const int num2;
    func(&num2); // void func(const int* p)
    return 0;
}
构成函数重载的条件:

1、同一个作用域

2、函数名相同

3、参数列表不同(参数的个数、类型,指针、引用是否加const)

函数重载的原理:

C++中的函数重载并不是真正的重名,而是在编译时编译器会把函数的参数列表信息追加到函数名的末尾,也就是在编译时函数名经历的换名的过程,在函数调用时,编译器会把实参的类型信息追加到函数名的末尾,这样就知道该调用哪个函数了。

// _Z4funcPi:
void func(int* p)
{
    cout << "func int* p" << endl;
}
​
// _Z4funcPKi:
void func(const int* p)
{
    cout << "func const int* p" << endl;
}
​
// _Z4funcii:
void func(int num,int num1)
{
    cout << "func const int int" << endl;
}
C++中如何使用C的库文件:
面临的问题:

1、把函数声明头文件导入后,默认情况下g++会按照C++的语法声明函数,会把C头文件中的函数声明进行换名。

2、g++在编译调用函数的语句时,会先尝试调用换名的函数,此时发现已经声明过,所以编译器生成的换名的函数调用指令,而C的库文件中的函数没有换名,所以就会调用失败,也就链接失败。

解决方法:

1、使用extern "C" 包含一下C函数声明,这个语句的功能就是告诉编译器按照C语言的处理方式编译函数声明,也就是不要对函数进行换名。

2、g++在编译调用函数的语句时,会先尝试调用换名的函数,此时会发现没有该版本的函数声明,然后再尝试调用未换名的函数,这样编译器生成就是未换名的函数调用指令,就能成功调用C的库文件中的函数。

注意:函数重载不是为实现通用代码,只为了提高代码的可读性(降低取名难度)。

练习:使用函数重载技术实现一个基本类型通用的冒泡排序函数。

signed char         
signed short        
signed int          
signed long        
   
unsigned char       
unsigned short     
unsigned int        
unsigned long       
    
单精度: float           
双精度: double          
​
字符串类型

2、默认形参

1、在声明函数时,可以给函数的形式参数设置一个默认值,当调用函数时,设置过默认值的参数位置可以不提供实参,会使用默认值。

#include <iostream>
using namespace std;
​
void func(int num1=1234,int num2)
{
    cout << num << endl;
}
​
int main(int argc,const char* argv[])
{
    func(123);
    return 0;
}

2、设置默认值的参数要连续且靠右,因为调用者提供的实参要优先提供给没有设置默认的参数使用。

// 错误写法
void func(int num1,int num2=1234,int num3=456)
{
    cout << num1 << " " << num2 << " " << num3 << endl;
}
​
int main(int argc,const char* argv[])
{
    func(1,2,3);
    return 0;
}

3、如果函数的声明和定义分开实现,则只能在声明函数时设置默认形参,定义函数时不能设置默认形参,因为没有意义。

void func(int num1,int num2,int num3=1234);
​
int main(int argc,const char* argv[])
{
    func(1,2);
    return 0;
}
​
void func(int num1,int num2,int num3)
{
    cout << num1 << " " << num2 << " " << num3 << endl;
}

函数设置默认形参可能会造成调用时的冲突,如果该函数进行重载,又对部分函数设置的默认形参,就可能导致调用函数时有多个备选方案,造成调用冲突。

void func(int num1,int num2)
{
    cout << num1 << " " << num2 << endl;
}
​
void func(int num1,int num2,int num3=1234)
{
    cout << num1 << " " << num2 << " " << num3 << endl;
}
​
int main(int argc,const char* argv[])
{
    func(1,2);
    return 0;
}
3、内联函数:

C++标准委员会设计一种特殊函数,函数在声明时在返回值类型的前面增加 inline 关键字,当调用该函数时,编译器不会生成跳转指令,而是函数的二进制指令直接拷贝到调用位置,这样执行该函数时,直接执行二进制指令,不需要跳转,也不需要返回,所以执行速度比普通函数快,就像宏函数,但过多使用会造成冗余,代码段变大。

注意:内联函数只是C++标准委员会设计方案,具体是否内联要看编译厂商是否实现了内联功能。

编译器优化:

gcc/g++ -On 设置编译器的优化级别

默认情况下是-O0,不进行内联,-O2以上才会进行内联。

与宏函数的相同点和不同点:
相同点:

没有跳转、返回过程,提高代码执行速度,都会造成过多使用会造成冗余,代码段变大。

不同点:

1、内联函数会检查参数的类型和个数,宏函数只会检查参数的个数而不会检查类型,内联函数比宏函数安全。

2、宏函数提供任何类型的参数都可以调用,但内联函数的实参只能使用部分类型,宏函数比内联函数的通用性强。

3、内联函数可以有返回值,而宏函数没有。

什么样的函数适合设置为内联函数:

与宏函数一样,请参考C语言的笔记。

注意:结构、联合、类的成员函数默认都设置为了内联函数,这种内联被称为隐式内联,使用inline修饰的函数被称为显式内联。

五、堆内存管理的区别

C语言的内存管理:

1、C语言中没有堆内存管理的语句,而是在C标准库中提供了一套堆内存管理的函数,在使用时还需要包含stdlib.h头文件。

2、malloc系列函数申请堆内存时需要提供字节数,可能会出现字节数不够或过多的情况。

3、malloc系列函数返回的是void类型的地址,如果想在C++中继续使用malloc系列函数,则必须对返回值进行强制类型转换,原因就是在C++中void类型的地址不能再自动转换成其它类型的地址。

4、malloc系列函数不能对申请到的内存设置初值,只有calloc函数可以把申请到的内存初始化0,或者之后调用memset、bzero函数进行初始化。

5、在C++中malloc系列函数为结构、联合、类对象申请、释放内存时,不会自动调用构造、析构函数。

6、在申请数组型的内存块时,可以使用malloc或calloc,释放时也使用free函数。

7、malloc系列函数申请内存失败时会返回NULL地址。

C++的堆内存管理:

1、C++中有堆内存管理的语句,可以new、delete运算符管理堆内存,直接使用不需要包含任何头文件。

2、new运算符在申请内存时只需要提供数据类型,它会自动计算所需要的字节数,每次申请的字节数都刚刚好,不多不少。

3、new运算符返回的是带类型的地址,申请时提供是什么类型,返回的地址就是什么类型的。

4、new在申请内存时,还可以对申请到的内存设置初值。

5、new/delete为结构、联合、类对象申请、释放内存时,会自动调用构造、析构函数。

6、在申请数组型的内存块时,使用new 类型[n],释放内存时使用delete[],不能与new/delete不能混用,因为new[]/delete[]会自动调用n次构造、析构函数,而new/delete只调用一次构造、析构函数。

7、当使用new申请内存失败时,会抛出std::bad_alloc异常,而不是返回空地址。

#include <iostream>
#include <stdlib.h>
using namespace std;
​
struct Student
{
    int id; 
    char name[20];
    short age;
    float score;
    Student(void)
    {   
        cout << "构造函数" << endl;
    }   
    ~Student(void)
    {   
        cout << "析构函数" << endl;
    }   
};
​
int main(int argc,const char* argv[])
{
    int* p = new int(123456);
    cout << *p << endl;
    delete p;
​
    /*  
    Student* stup = new Student;
    delete stup;
    */
    /*  
    Student* stup = (Student*)malloc(sizeof(Student));
    free(stup);
    */
    /*  
    Student* stus = new Student[~0];
    delete[] stus;
    */
    return 0;
}
new/delete和malloc/free的相同点:

1、都可以管理堆内存。

2、返回的都是地址,只是类型不同。

3、都必须配合指针变量使用。

4、delete和free都可以释放空指针,但都不能重复释放。

重点掌握面试题:malloc/free 和 new/delete 的区别? 身份: 函数 关键字/运算符 返回值: void* 对应类型的指针 参数: 字节个数(手动计算) 类型(自动计算) 连续内存:手动计算总字节数 new[个数] 扩容: realloc 无法直接处理 失败: 返回NULL 抛异常 构造\析构: 不调用 调用 初始化: 不能初始化 可以初始化 头文件: stdlib.h 不需要 函数重载: 不允许重载 允许 内存分配的位置: 堆内存 自由存储区 注意:自由存储区是一个抽象的概念,而不是具体某个位置段,平时一般称new是分配在堆内存也问题不大,因为new底层默认调用了malloc,所以此时称分配在堆内存没问题,但是new可以像运算符一样被程序员重载或借助 new(地址) 类型 两种方式分配内存时,可以分配到其他内存段,所以称为自由存储区 笔试题: 现在有一块已经分配好的内存(堆、栈) 如何让通过new新申请的类对象、结构变量使用这块内存

常考面试、笔试题:new/delete和malloc/free的相同点和不同点?
问题:delete[] 释放内存时为什么可以调用n次析构函数。

使用new[] 申请数组型的堆内存时(结构、联合、类对象),所申请内存块的前4个字节,记录着内存块的块数,所以使用delete []释放内存时可以准确调用n次析构函数。

    Student* stus = new Student[13];
    cout << *((int*)stus-1) << endl;
    delete[] stus;
问题:在C++中如何把已有一块内存(从操作系统获取到的内存,例如:ipc共享内存)分配给结构、联合、类对象,能自动调用构造、析构函数。
// new(内存地址) 类型; new运算符可以重新解释一块内存,并自动调用构造函数
int main(int argc,const char* argv[])
{
    void* ptr = malloc(sizeof(Student));    
    cout << ptr << endl;
    Student* stup = new(ptr) Student;
    cout << stup << endl;
    delete stup;
    return 0;
}

六、引用和指针

什么是引用:

引用是一种取名机制,它可以给变量重新取一新的名字,所以引用也叫别名。

为什么使用引用:

1、跨函数共享变量,把函数的参数设置引用,可以在函数内共享实参变量,并且是否共享实参变量由函数的实现者决定,这种设计给了函数实现者权限,并且给了函数调用者方便。

#include <iostream>
using namespace std;
​
void func(int& dashixiong)
{
    dashixiong = 5678;
    cout << &dashixiong << " " << dashixiong << endl;
​
}
​
int main(int argc,const char* argv[])
{
    int sunlingling = 1234;
    int& dashixiong = sunlingling;
    
    func(sunlingling);
    cout << &sunlingling << " " << sunlingling << endl;
    return 0;
}

2、提高函数的传参效率,默认情况下,函数传参单向值传递,变量有多少个字节就要拷贝多个字节的数据,而传递变量的地址,可以需要拷贝4字节的数据,而使用引用一个都不需要拷贝,它比指针的传参的效率还要高。

#include <iostream>
using namespace std;
​
struct Data
{
    char str[20];
    int num;
    char ch;
};
​
void func(Data& d)
{
​
}
​
int main(int argc,const char* argv[])
{
    Data d;
    for(int i=0; i<200000000; i++)
    {
        func(d);
        d.num++;
    }   
    return 0;
}
使用引用要注意的问题:

引用在定义时必须初始化,所以不可能存在空引用,但可能存在悬空引用,但使用指针可能空指针、野指针,所以使用引用比使用指针安全。

#include <iostream>
using namespace std;
​
int& func(void)
{
    int num = 1234;
    return num;
}
​
int main(int argc,const char* argv[])
{
    /*  
    int& hehe = func();
    cout << hehe << endl;
    */
    int* p = new int(1234);
    cout << *p << endl;
    int& num = *p; 
    delete p;
    cout << num << endl;
    return 0;
}

可以引用常量数据、字面值数据,但需要定义const类型的引用。

int main(int argc,const char* argv[])
{
    const int num = 1234;
    const int& hehe = num;  
    const int& xixi = 56789;
​
    cout << hehe << endl;
    cout << num << endl;
    cout << xixi << endl;
​
    return 0;
}

函数的参数使用引用时,实参变量就有被修改的风险,为了防止实参变量被破坏,可以使用const修改引用。

void func(const int& num)
{   
    num = 23456789;
}
​
int main(int argc,const char* argv[])
{   
    int num = 1234;
    func(num);
    cout << num << endl;
​
    return 0;
}

可以引用一个数组(定义数组的引用),但不能定义引用型的数组。

int main(int argc,const char* argv[])
{
    int arr[5] = {1,2,3,4,5};
    // 可以定义数组指针
    int (*arr1)[5] = &arr;
    // 可以定义指针数组
    int* arr2[5];
    
    // 可以定义数组引用或引用数组
    int (&arr3)[5] = arr;   
​
    // 不可以定义引用型的数组
    int& arr4[5];
​
    return 0;
}

总结:在C++中尽量多使用引用,少使用指针。

常考笔试、面试题指针和引用的相同点和不同点?

七、类型转换的区别

隐式类型转换:

C语言和C++语言隐式类型转换区别不大,仅的区别有:

1、整数数据不能再隐式转换成枚举。

2、void类型的指针不能再隐式转换成其它类型的指针。

强制类型转换:

1、C语言中的强制类型转换语法在C++中还可以继续使用,但官方不建议这样,因为有安全隐患。

2、C++中设计出一套新强制类型转换的规则:

reinterpret_cast<目标类型>(数据)
static_cast<目标类型>(数据)
const_cast<目标类型>(数据)
dynamic_cast<目标类型>(数据)

3、虽然使用起来比较麻烦,没有C语言方便,但是它能检查程序员转换是否安全,并提示错误。

4、之所以设计这么复杂就是为了让程序员记不住,不使用强制类型转换,因为C++之父本贾尼·斯特劳斯特卢普认为好的代码设计就不应该使用强制类型转换,当需要强制类型转换时,他希望程序员去优化自己的代码不是使用强制类型转换。

八、操作符别名

C、C++语言中使用的运算符或符号,在个别地区的键盘上是没有的,为了让所有人都使用C++进行编程,所以就对个别的字符取了别名。

|| 等价于 or 
&& 等价于 and 
{  等价于 <% 
}  等价于 %>
#include<iostream>
using namespace std;
​
int main (void)
<%
    int i = 1,j =  0;
    if (i or j)
    <%
        cout << "true" << endl;
    %>
    else
    <%
        cout << "false" << endl;
    %>
    return 0;
%>

C语言与C++语言的区别有哪些?

1、Hello代码的区别

2、数据类型的区别

基本类型、复合类型

3、函数的区别

函数重载、形参默认值、内联函数

4、堆内存管理的区别(malloc、free与new、delete的区别)

5、函数传参的区别,指针和引用

6、类型转换

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值