Featured image of post C到C++过度

C到C++过度

引用

引用:给一个已有对象取一个别名

就是复制变量的源地址

1
2
3
4
int a = 100;
int &ra = a;
a = 200;
ra = 200;	//a与ra等价

注意点:

  • 引用必须在定义的同时赋值,不可单独定义引用,例如 int &r; 是错误的。
  • 引用一方面提高了数据传输的效率,另一方面简化了数据表达的样式(跟指针相比)

类型转换

旧式类型转换

1
2
(char)a; // C语言风格类型转换
char(a); // C++风格类型转换

新式类型转换

1
2
3
const_cast<char *>(a);
static_cast<char *>(a);
dynamic_cast<char *>(a);
  • const_cast : 专用于去除指针或引用的 const 属性
  • static_cast : 与旧式转换相近,但提供了更易于查找的语法,并能有效识别不兼容类型。
  • dynamic_cast : 专用于类类型的上下代际间的转换

新式转换const_cast

  • const_cast 旨在去除标识符的cv限定属性(即 const 与 volatile
  • const_cast 只能作用于指针或引用类型
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
int main()
{
    int i = 6;
    const int &ri = i;		//const 引用,数据不可修改
    
    //错误示范
    ri = 8;
    
    //正确的
    const_cast<int &>(ri) = 8;
}

处理常目标指针

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
int main()
{
    int i = 6;
    const int *p = &i;	//常量指针,不可修改目标
    
    //错误
    *p	= 8;
    
    //正确,去除const特性后,可以修改
    *(const_cast<int *>(p)) = 8;
    
    // 错误,试图扩大权限
	int *k = p;
    
	// 正确,去除 const 特性后,可赋值给普通指针 k
    int *k = const_cast<int *>(p);

}

虽然 const_cast 可以将常目标指针的 const 属性剔除掉,但它不能剔除普通变量和常指针本身的const 属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int main()
{
    int i=6;
    int *const p = &i;	//指针常量
    int j=9;
    
    //错误
    p = &j;
    
    //错误,const_cast不能去除常量指针的const属性
    const_cast<int *>(p) = &j;
}

新式转换 static_cast

static 意味着静态转换,静态的含义是操作的过程只发生在编译阶段,而不是运行阶段,静态转换不涉及类型推理。

用法:

增加可读性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int	main()
{
    float f = 314;
    
    //旧式
    int i = (int) f;
    int i = int(f);
    
    //新式,等价于旧式转换
    //新式静态强转具有可读性,容易查找
    int i = static_cast<int>(f);
}

提高安全性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int main()
{
    int i = 6; // 普通整型数据
    float *pf; // 普通浮点指针

    // 旧式转换不进行任何合理性检查
    // 下面的代码,可以将类型问题瞒天过海
    // 编译器让其畅行无阻,开发者肉眼也难以察觉
    pf = (float *)&i; // float * 与 int * 不兼容,照样通过编译

    // 使用 static_cast 遇到非兼容性类型转换,会提出警告甚至错误
    pf = static_cast<float *>(&i); // float * 与 int * 不兼容
}

注意,虽然 static_cast 可以将不兼容的普通数据类型转换明确指出来(报错),但对于代际类类型的上下层转换无能为力,对于类类型的代际上行转换或下行转换,需要借助 dynamic_cast 去保证运行的安全性。

关键字auto

在C语言中,auto 用来声明一个存储在栈的变量,因此它只能用来修饰临时变量,不能用来修饰全 局变量、静态变量,与此同时临时变量本身默认就是存储在栈中,因此在C语言中,auto 基本上是作废的。 在C++中,auto 代表自动获得数据的类型,比如:

1
auto a = 100; // 等价于: int a = 100;

使用auto 关键字可以对具体的类型进行模糊(泛化),如果某一个功能的操作流程式固定,唯一 的不同在于数据的类型,这时候就可以简单地使用auto关键字对类型进行模糊的操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>  

using std::cout;
using std::endl;
using std::string;

// 通过auto简单粗暴地模糊数据的类型
// 让该函数可以接收任意类型的参数传递
auto Add(auto a, auto b)
{
    return a + b;
}

int main(int argc, char const *argv[])
{
    int Num = Add(123, 567);
    cout << Num << endl;

    float Num2 = Add(123.333, 567.222);
    cout << Num2 << endl;

    string s1 = "Hello";
    string s2 = "World";
    auto str = Add(s1, s2);
    cout << str << endl;  // 这里修复:en → endl

    return 0;
}

函数默认参数

C++允许一个函数的参数有默认值,例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

// 函数的声明
// 右侧的参数 b 如果没有传递,则默认值为3.14
void f(int a, float b = 3.14)
{
    cout << "a = " << a << ", b = " << b << endl;
}
int main()
{
    // 显式给 b 传递参数1.23
    f(100, 1.23);

    // 使用 b 的默认值3.14
    f(200);

    return 0;
}
  • 为了防止出现二义性,具备默认值的参数只能位于参数列表的最右边
  • 如果函数定义与函数声明不在同一文件,一般只为声明指定默认参数,定义处不写默认参数。

函数重载

就是相同的函数名,有不同的参数。

在C语言中,相同作用域内的重名函数会引起冲突,哪怕它们拥有不同的参数列表也不行,而在 C++中,重名函数只要满足一定的要求,可以同时存在,这大大拓展了函数设计的灵活性。这种重名且可并存的语法机制,被称为函数重载

C++中大量存在重载的函数,这些同名函数实际上就是一个函数的不同版本。

1
2
3
4
5
string ( const string& str );
string ( const string& str, size_t pos, size_t n = npos );
string ( const char * s, size_t n );
string ( const char * s );
string ( size_t n, char c );

这些名称相同的函数,都存在于同一名空间中(作用域),怎样才能形成合理合法的重载而不会冲突呢?这个问题的答案比较复杂,总体的基本原则是,只要让系统在使用它们的时候,可以有效地区分各个不同的版本,那它们就可以形成合法的重载版本。

可以重载的情形

  • 参数个数不同(注意默认参数)
  • 参数类型不同
  • 参数列表的顺序不同
  • 类方法(即类内部的函数)的 const 属性可以构成重载
  • 普通指针与常目标指针可以构成重载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 参数个数不同,可以形成重载
void f1(int a);
void f1(int a, int b);

// 参数类型不同,可以形成重载
void f2(int a);
void f2(float b);

class A
{
    // 类方法的const属性,可以形成重载
    void f3() const;
    void f3();
};

// 普通指针与常目标指针,可以形成重载
void f4(      char *p);
void f4(const char *p);

不可以重载的情形

  • 函数名、函数参数列表完全一致
  • 函数的返回值类型差异。
  • 静态函数声明(static)。
  • const 型变量(包括常指针)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 函数名、参数列表完全一致,仅靠返回值类型的差异,将无法形成重载,这两个函数将会冲突
void f1(int a);
float f1(int a);

// static不能形成重载,以下两个函数将会冲突
int f2(int a);
static int f2(int a);

// const型变量(包括常指针),不能形成重载
void f3(      int a);
void f3(const int a);

void f4(char *p);
void f4(char * const p);

引用形参

引用类型的参数是否可以形成重载,要根据实参的具体情况来定:

  • 如果实参为常量,那么可以重载。
  • 如果实参为变量,那么不可以重载。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
void f(int  a);
void f(int &a);

int main()
{
    int m = 100;

    // ×:会引起二义性,两个版本都能跟参数完全匹配
    f(m);

    // √:可以顺利调用,因为普通引用类型无法指向常量
    f(6);
}

引用类型的参数会很容易引起冲突,因此一般而言不要把引用作为重载的条件。

堆内存管理

在 C 语言中,使用 malloc()free() 来分配和释放堆内存,这两个函数在 C++ 中也一样可用,但 C++ 有更好的 API 来实现这一功能:

  • 分配堆内存:new
  • 释放堆内存:delete
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(void)
{
    // 申请一块int型数据大小的堆内存
    int *p1 = new int;
    int *pp1 = malloc(sizeof(int));

    // 申请一块可以存储100个int型数据大小的堆内存
    int *p2 = new int[100];
    int * pp2 = calloc(100 , sizeof(int));

    // 申请一块35个字节大小的堆内存
    char *p3 = new char[35];
    char * pp2 = calloc(35 , sizeof(char));

    // 释放所有的堆内存资源
    delete p1;
    delete [] p2;
    delete [] p3;

    free(pp1);
    free(pp2);
    free(pp3);
}

从以上代码可以看出,new 和 delete 的语法是很简单的,唯一需要注意的是,当分配和释放多个 连续内存块(数组类型的),需要加上方括号 [ ]

与 C 语言的 malloc()free() 一样,newdelete 只能用来管理堆内存,不能用来操作栈内存、静态内存等其他内存空间,且在释放内存的时候只能一次性释放完毕(而且释放的目标参数必须是首地址),不允许释放一部分。

可以看到,在操作基本数据类型的时候,这两组堆内存操作函数是没有区别的,它们真正的区别在给类对象分配内存的时候,具体而言指的是:

  • 申请类类型的内存时,new 会自动调用类的构造函数,而 malloc() / calloc() 不会。
  • 释放类类型的内存时,delete 会自动调用类的析构函数,而 free() 不会。

枚举循环

从C++11开始,C++对for语句引入了枚举循环的概念,专用于具有确定元素个数的数组、容器或集合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>
using namespace std;

int main(int argc, char const *argv[])
{
    int a[3] = {1,2,3};

    // 枚举数组a中的各个元素,并输出它们
    for(int i : a)
        cout << i << endl;

    return 0;
}

循环的过程就是让变量 i 逐个取得数组 a 中的各个元素

循环次数不再由程序代码指定, 而是由待枚举的数据的元素个数决定

最后更新于 2026-04-06 10:39