C++区别于C语言的提升用法(万字总结)

1.namespace产生原因

在C语言中,变量,函数,以至于类都是大量存在的,因此会产生大量的名称存在于全局作用域中,可能产生很多冲突,至此c++的祖师爷为避免命名冲突和名字的污染,造出来了关键字namespace来解决这种问题

1.2查找规则

c/c++规定用任何变量类型的函数都要向上访问,在向上的过程中去找到他的出处在编译时候查找没有在去全局中找。

例如;

在c语言中rand是包含在头文件#include<stdlib,h>中的函数,因此编译器无法判断是函数还是变量。但事实上用c++可以完美解决。

2.namespace的特点

2.1用namespace来定义命名空间

后加名字加{成员},可定义变量/函数/类型。

1.在命名空间中定义的变量。

2.定义函数。

3.定义结构体类型。

2.2本质是定义一个全局域各自独立

如2.1中的a在不同的域中可以定义同名且不冲突。

2.3 c++中的域

函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响 编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。

局部域

局部域是指在函数、代码块(如ifforwhile语句块)内部定义的变量所处的作用域。在局部域中定义的变量,其生命周期从定义的位置开始,到所在的代码块结束时终止。当程序执行离开该代码块时,局部变量所占用的内存会被自动释放。

复制代码
#include <iostream>

void testLocalScope() {
    // 局部变量 a,生命周期从这里开始
    int a = 10;
    std::cout << "Local variable a: " << a << std::endl;
    // 代码块结束,局部变量 a 的生命周期结束
}

int main() {
    testLocalScope();
    // 这里无法访问局部变量 a
    return 0;
}

全局域

全局域是指在所有函数、类和命名空间之外定义的变量所处的作用域。全局变量的生命周期从程序开始执行时就已经分配内存,直到程序结束时才会释放内存。

复制代码
#include <iostream>

// 全局变量 b,生命周期从程序开始
int b = 20;

void testGlobalScope() {
    std::cout << "Global variable b: " << b << std::endl;
}

int main() {
    testGlobalScope();
    // 全局变量 b 仍然可以访问
    std::cout << "Global variable b in main: " << b << std::endl;
    // 程序结束,全局变量 b 的生命周期结束
    return 0;
}

命名空间域

命名空间域主要用于解决命名冲突的问题,它只是对标识符进行逻辑上的分组,并不影响变量的生命周期。在命名空间中定义的变量,其生命周期取决于它是全局变量还是局部变量。

复制代码
#include <iostream>
// 全局命名空间
namespace GlobalNS {
    int globalVar = 10;

    void globalFunction() {
        std::cout << "This is a function in the global namespace. globalVar = " << globalVar << std::endl;
    }
}
void testLocalNamespace() {
    // 局部命名空间
    namespace LocalNS {
        int localVar = 20;

        void localFunction() {
            std::cout << "This is a function in the local namespace. localVar = " << localVar << std::endl;
        }
    }

    // 使用局部命名空间中的变量和函数
    std::cout << "Value of localVar in LocalNS: " << LocalNS::localVar << std::endl;
    LocalNS::localFunction();

    // 也可以使用全局命名空间中的变量和函数
    std::cout << "Value of globalVar in GlobalNS: " << GlobalNS::globalVar << std::endl;
    GlobalNS::globalFunction();
}

int main() {
    // 可以直接在 main 函数中使用全局命名空间
    std::cout << "Value of globalVar in GlobalNS (from main): " << GlobalNS::globalVar << std::endl;
    GlobalNS::globalFunction();

    // 调用包含局部命名空间的函数
    testLocalNamespace();

    // 尝试在 main 函数中使用局部命名空间(这是错误的,因为局部命名空间超出了作用域)
    // 下面这行代码会导致编译错误
    // std::cout << LocalNS::localVar << std::endl;

    return 0;
}

域的名字在自己内部只能用一次,同一个域名字不能重复,但不同的域可以

2.4 namespace只能定义在全局,当然他还可以嵌套定义


图1;嵌套后要区别调用

图二注:当库很大或项目大还可以套中套来解决命名冲突

2.5项⽬⼯程中多⽂件

项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。

多⽂件中可以定义同名namespace,他们会默认合并到⼀起,就像同⼀个namespace⼀样

定个文件一个命名空间都会封在一起

例如:当同时定义栈与队列 会产生多个文件

3.命名空间使用

编译查找⼀个变量的声明/定义时,默认**只会在局部或者全局查找,不会到命名空间⾥⾯去查找。**所以 下⾯程序会编译报错。所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:

3.1指定命名空间访问

不易出错,推荐使用

3.2 展开命名空间中全部成员(展开头文件与命名空间的区别)

展开命名空间中全部成员--》变成全局变量因此可能造成命名冲突

展开头文件是将头文件在预处理阶段将头文件的代码拷贝过来

不推荐,冲突风险大,但是在小练习中为方便推荐使用。

3.3折中using将命名空间中某个成员展开

当a用的不多但是b用的多是将b解开变成全局;

项⽬中经常访问的不存在冲突的成员推荐这种⽅式

前提了解

<< 是流插入运算符,**>>**是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移)

cout/cin/endl 等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中

C++输⼊&&输出&&换行

1包含文件**#include<iostream>**

是Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象。

2输出

std::cout是iostream类的对象,它主要⾯向窄字符的标准输出流。

cpp 复制代码
#include <iostream>
int main()
{
	int a = 0;
	double b = 0.1;
	char c = 'x';
//自动识别变量类型
	std::cout << a << std::endl;
	std::cout << b << " " << c << std::endl;
	return 0;
}

好处:1.连续输出并且与printf可以一起使用2.可自动识别所输出的类型

坏处:此用法控制输出的小数繁琐相比用printf更好用些

3输入

std::cin是istream类的对象,它主要⾯向窄字符的标准输⼊流。

cpp 复制代码
#include <iostream>
int main()
{
	int a = 0;
	double b = 0.1;
	char c = 'x';
	std::cin >> a;
	std::cin >> b >> c;
	std::cout << "输出"<<std::endl;
	std::cout << a << std::endl;
	std::cout << b << " " << c << std::endl;
	return 0;
}

注:cout/cin/endl 等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要 通过命名空间的使⽤⽅式去⽤他们。

4.换行

std::endl是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。

相比与在C语言中学到的"/n"在c++中std::endl在不同平台上都可以运行

上述图片有其应用

缺省函数

1.缺省参数说明

将声明或定义函数时的参数指定一个值(该值即为缺省值)

注:当实参没有指定数值就采用缺省值否则采用实参。

cpp 复制代码
#include <iostream>
#include <assert.h>
using namespace std;
void Func(int a = 0)//在形参后面加个值
{
	cout << a << endl;
}
int main()
{
	Func(); // 没有传参时,使⽤参数的默认值 
	Func(10); // 传参时,使⽤指定的实参 
	return 0;
}

2.缺省参数的分类

全缺省就是全部形参给缺省值

半缺省就是部分形参给缺省值

注 :C++规定半缺省参数必须从**右往左 依次连续缺省,**不能间隔跳跃给缺省值。

3.应用

例如,在栈构建空间时会动态开辟通常以二的倍速增加一定就会亏损,因此控制空间大小显得尤为重要。

cpp 复制代码
// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
 STDataType* a;
 int top;
 int capacity;
}ST;
void STInit(ST* ps, int n = 4);
// Stack.cpp
#include"Stack.h"
// 缺省参数不能声明和定义同时给 
void STInit(ST* ps, int n)
{
 assert(ps && n > 0);
 ps->a = (STDataType*)malloc(n * sizeof(STDataType));
 ps->top = 0;
 ps->capacity = n;
}
// test.cpp
#include"Stack.h"
int main()
{
 ST s1;
 STInit(&s1);
 // 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容 
 ST s2;
 STInit(&s2, 1000);
 return 0;
}

在初始化中确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容

注:当文件中声明与定义分开时,缺省参数只等在声明中给缺省值。

因为:在定义中只有在编译的时候才会调用。

c++函数重载

C++⽀持在同⼀作⽤域中出现同名函数

但是分为参数类型不同,参数个数不同,参数顺序不同

1.参数类型不同

cpp 复制代码
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return 0;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return 0;
}
int main()
{
	Add(10, 20);
	Add(10.1, 20.2);
	return 0;
}

2、参数个数不同

cpp 复制代码
#include <iostream>
using namespace std;
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}
int main()
{
	f();
	f(10);
	return 0;

3、参数类型顺序不同(类型不同)

cpp 复制代码
#include <iostream>
using namespace std;
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}
int main()
{
	f(10, 'a');
	f('a', 10);
	return 0;
}

特殊的

1.返回值不同

不能作为重载条件,因为调⽤时也⽆法区分

cpp 复制代码
void fxx()
{
}
int fxx()
{
 return 0;
}

2.一个参数问题

f()但是调⽤时,会报错,存在歧义,编译器不知道调⽤谁

cpp 复制代码
void f1()
{
 cout << "f()" << endl;
}
void f1(int a = 10)
{
 cout << "f(int a)" << endl;
}

引用

一,概念

是对已知的变量取别名,并与它引用的对象共用同一块空间

类型& 引⽤别名 = 引⽤对象;

复制代码
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;

就像在《西游记》中孙悟空(变量)可以叫孙大圣悟空猴哥。这里指的全是他一个人(空间)。

但是,不得不🤮一下祖师爷当初设计的与C语言相同符号&,让后续辨析&费了很大劲。

二,引用的特征

•🔪 引⽤在定义时必须初始化

复制代码
int main()
{
int a = 10;
int& ra;
return 0;
}

•🔪🔪⼀个变量可以有多个引⽤

复制代码
int main()
{
int a = 0;
int& b = a;
int& c = a;
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
return 0;
}
复制代码
typedef unsigned int uint;

这⾥取地址我们看到是⼀样的

•🔪🔪🔪 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体

复制代码
int main()
{
	int a = 0;
	int d = 1;
	int& b = a;
	int& b = d;
	return 0;
}

三 ,引用的使用

🥤引用传参->减少拷贝提高效率

图一中形参是实参的拷贝有4*1000=4000个字节

🌸图二图三都是引用传参的方法

辨析图二为传指针占4/8个字节,而图三是别名不另开空间

🥤引用作返回值->改变引用对象的同时改变被引用对象

在我们在实现栈中有top取栈顶元素

❌ ❌ ❌ 中间商 ❌ ❌ ❌ ✅ ✅ ✅ 无中间商 ✅ ✅ ✅

取栈顶元素中为什么会❌ 呐

因为c/c++规定返回值存在临时寄存器(中间商)中,所以被引用对象改变时已经销毁了产生****野指针****(进栈创建出栈就销毁)通俗->(进函数创建出函数就销毁)-

那传引用为什么✅那

返回的栈是在堆中建立的所以没有了野指针且返回的是别名无临时变量(中间商)

引用传参

在函数调用时,若采用值传递,会把实参的值复制一份给形参,这在处理大型对象(如大型结构体、类对象)时,会带来较大的性能开销。而引用传参则是将实参的引用传递给形参,不会进行对象的拷贝,从而提高效率。此外,由于引用是对象的别名,对引用形参的修改会直接影响到实参

复制代码
#include <iostream>
#include <string>

// 引用传参
void modifyString(std::string& str) {
    str += " - Modified";
}

// 值传参
void modifyStringByValue(std::string str) {
    str += " - Modified by value";
}

int main() {
    std::string original = "Hello";
    std::cout << "Before modification: " << original << std::endl;

    // 调用引用传参的函数
    modifyString(original);
    std::cout << "After modification by reference: " << original << std::endl;

    std::string another = "World";
    std::cout << "\nBefore value modification: " << another << std::endl;
    // 调用值传参的函数
    modifyStringByValue(another);
    std::cout << "After value modification: " << another << std::endl;

    return 0;
}

代码解释

  • modifyString 函数使用引用传参,直接对传入的字符串对象进行修改,调用该函数后,original 字符串会被改变。

  • modifyStringByValue 函数使用值传参,会创建传入字符串的一个副本,对副本的修改不会影响到原始字符串。


引用做返回值

  1. 返回全局变量的引用

    #include <iostream>

    // 全局变量
    int globalValue = 10;

    // 返回全局变量的引用
    int& getGlobalValue() {
    return globalValue;
    }

    int main() {
    // 获取全局变量的引用
    int& ref = getGlobalValue();

    复制代码
     std::cout << "Original value: " << ref << std::endl;
    
     // 通过引用修改全局变量的值
     ref = 20;
    
     std::cout << "Modified value: " << globalValue << std::endl;
    
     return 0;

    }

  • globalValue 是一个全局变量,其生命周期贯穿整个程序。

  • getGlobalValue 函数返回 globalValue 的引用,在 main 函数中通过引用 ref 可以直接访问和修改 globalValue 的值。

  1. 返回类成员变量的引用

    #include <iostream>

    class MyClass {
    private:
    int memberValue;
    public:
    MyClass(int value) : memberValue(value) {}

    复制代码
     // 返回类成员变量的引用
     int& getMemberValue() {
         return memberValue;
     }

    };

    int main() {
    MyClass obj(30);

    复制代码
     // 获取类成员变量的引用
     int& ref = obj.getMemberValue();
    
     std::cout << "Original member value: " << ref << std::endl;
    
     // 通过引用修改类成员变量的值
     ref = 40;
    
     std::cout << "Modified member value: " << obj.getMemberValue() << std::endl;
    
     return 0;

    }

  • MyClass 类包含一个私有成员变量 memberValue

  • getMemberValue 函数返回 memberValue 的引用,在 main 函数中通过引用 ref 可以直接访问和修改 memberValue 的值。

返回数组元素的引用

复制代码
#include <iostream>

// 函数返回数组元素的引用
int& getArrayElement(int arr[], int index) {
    return arr[index];
}

int main() {
    int myArray[5] = {1, 2, 3, 4, 5};

    // 获取数组元素的引用
    int& ref = getArrayElement(myArray, 2);

    std::cout << "Original array element: " << ref << std::endl;

    // 通过引用修改数组元素的值
    ref = 10;

    std::cout << "Modified array element: " << myArray[2] << std::endl;

    return 0;
}

代码解释

  • getArrayElement 函数接受一个数组和一个索引作为参数,返回数组中指定索引位置元素的引用。

  • main 函数中通过引用 ref 可以直接访问和修改数组元素的值。

注意事项

  • 避免返回局部变量的引用:局部变量在函数结束时会被销毁,返回其引用会导致未定义行为。例如:

    复制代码
    // 错误示例:返回局部变量的引用
    int& getLocalValue() {
        int localVar = 5;
        return localVar; // 错误:返回局部变量的引用
    }
  • 使用 const 引用 :如果不希望通过返回的引用修改对象的值,可以返回 const 引用。例如

    const int& getReadOnlyValue(const int& value) {
    return value;
    }

const引⽤

可以引⽤⼀个const对象,但是必须⽤const引⽤。const引⽤也可以引⽤普通对象,因为对象的访 问权限在引⽤过程中可以缩⼩,但是不能放⼤。

所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。

在类型转换中会产⽣临时对 象存储中间值,也就是时,b和c引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这⾥ 就触发了权限放⼤,必须要⽤常引⽤才可以。

解析例子

指针和引⽤的关系

C++中指针和引⽤就像两个性格迥异的亲兄弟,指针是哥哥,引⽤是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。

• 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。

• 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。

• 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。 • 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。

• sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下 占4个字节,64位下是8byte)

• 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些。

拓展

💩(辨析)#define与typedef与引用的不同

复制代码
#define N 10;

定义宏(#define)的常亮,N替换成宏用符号代替常量 好处:是预处理阶段将N换成10.

复制代码
typedef unsinged int uint;

typedef :给类型取别名

🪡引用的底层逻辑

​​

这是对引用和指针的汇编代码

发现划横线的地方相同---->>>引用的底层是指针

nullptr

了解nullptr之前我们要知道

  • ❓c++为什么不用NULL反而用nullptr。
  • ❓nullptr它与NULL的区别是什么?

NULL

是⼀个********

🍦🍦🍦在c++中被定义为从常量0,而C语言中被定义为无类型指针(void*)常量

会产生两种问题

复制代码
#include<iostream>
using namespace std;
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
f((int*)NULL);
// f((void*)NULL);
f(nullptr);
return 0;
}


🍞问题一

复制代码
f(NULL);

本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖
🍞🍞 问题二

复制代码
f((int*)NULL);
f((char*)NULL);

我们在调用空值指针返回空🈳的时候要强转不同类型。

有没有能直接调用的呢。

nullptr

  • 🌸🌸ta⼀个特殊的关键字(值为零)

  • 🌸ta可以转换成任意其他类型的指针类型<--相当于->f((类型*)NULL);

复制代码
  #include<iostream>
  using namespace std;
  void f(char* x)
  {
  	cout << "f(char* x)" << endl;
  }
  void f(int* ptr)
  {
  	cout << "f(int* ptr)" << endl;
  }
  int main()
  {
  	f(nullptr);
  	return 0;
  }

  • 一定要想好写的是什么在调用。

  • 如果不确定就强制类型转换

复制代码
  	f((int* )nullptr);
  	f((char*)nullptr);

内联函数的概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数的使用可以提升程序的运行效率。

我们可以通过观察调用普通函数和内联函数的汇编代码来进一步查看其优势:

复制代码
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int ret = Add(1, 2);

	return 0;
}

下图左是以上代码的汇编代码,下图右是函数Add加上inline后的汇编代码:

从汇编代码中可以看出,内联函数调用时并没有调用函数这个过程的汇编指令。

内联函数的特性

1、inline是一种以空间换时间的做法,省了去调用函数的额外开销。由于内联函数会在调用的位置展开,所以代码很长或者有递归的函数不适宜作为内联函数。频繁调用的小函数建议定义成内联函数。

2、inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有递归等,编译器优化时会忽略掉内联。

3、inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了链接就会找不到。

auto

auto的使用细则

一、auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时必须加&。

复制代码
#include <iostream>
using namespace std;
int main()
{
	int a = 10;
	auto b = &a;   //自动推导出b的类型为int*
	auto* c = &a;  //自动推导出c的类型为int*
	auto& d = a;   //自动推导出d的类型为int
	//打印变量b,c,d的类型
	cout << typeid(b).name() << endl;//打印结果为int*
	cout << typeid(c).name() << endl;//打印结果为int*
	cout << typeid(d).name() << endl;//打印结果为int
	return 0;
}

注意:用auto声明引用时必须加&,否则创建的只是与实体类型相同的普通变量。

二、在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

复制代码
int main()
{
	auto a = 1, b = 2; //正确
	auto c = 3, d = 4.0; //编译器报错:"auto"必须始终推导为同一类型
	return 0;
}

auto不能推导的场景

一、auto不能作为函数的参数

以下代码编译失败,auto不能作为形参类型,因为编译器无法对x的实际类型进行推导。

复制代码
int main()

简化代码,替代写起来长的类型

auto自动判断类型或别名

2.也可用于返回值与函数返回类型(不建议多用)

for流

概念

相关推荐
阿让啊21 分钟前
C语言中操作字节的某一位
c语言·开发语言·数据结构·单片机·算法
拾忆-eleven37 分钟前
C语言实战:用Pygame打造高难度水果消消乐游戏
c语言·python·pygame
草莓啵啵~1 小时前
搜索二叉树-key的搜索模型
数据结构·c++
共享家95271 小时前
深入理解C++ 中的list容器
c++
孞㐑¥1 小时前
C++11介绍
开发语言·c++·经验分享·笔记
云小逸1 小时前
【QQMusic项目界面开发复习笔记】第二章
c++·qt
李匠20241 小时前
C++ RPC以及cmake
网络·c++·网络协议·rpc
再睡一夏就好1 小时前
Linux常见工具如yum、vim、gcc、gdb的基本使用,以及编译过程和动静态链接的区别
linux·服务器·c语言·c++·笔记
YHY_13s2 小时前
访问者模式
c++·访问者模式
我也不曾来过12 小时前
list底层原理
数据结构·c++·list