【一】【C++】C++入门

发布时间:2024年01月22日

命名空间

命名空间namespace

使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染

 
/*1 rand与rand()冲突*/
#include <stdio.h>
#include <stdlib.h>
int rand = 10;

int main() {
    printf("%d\n", rand);
    return 0;
 }

stdlib.h库中有一个函数rand(),此时如果我们想要定义一个int类型,名字为rand的变量,此时就会报错。

 
/*2 在main中定义rand不会报错*/
#include <stdio.h>
#include <stdlib.h>
int main() {
    int rand = 10;
    printf("%d\n", rand);
    return 0;
 }

在main函数中定义rand变量,并且打印,此时不会报错。

 
/*3 在main中定义rand不会报错,但是此时rand()无法使用*/
#include <stdio.h>
#include <stdlib.h>
int main() {
    int rand = 10;
    rand = rand();
    printf("%d\n", rand);
    return 0;
 }

但是此时rand()无法使用。

这就是命名冲突。为了避免这种情况我们引入了命名空间namespace关键字。

命名空间namaspace的定义

 
/*4 引入命名空间*/
#include <stdio.h>
#include <stdlib.h>
namespace _240121 {
    int rand = 10;
    int add(int left, int right) {
        return left + right;
    }
    struct node {
        int data;
        struct node* next;
    };
 }

int main() {
    _240121::rand = rand();
    printf("%d\n", _240121::rand);
    return 0;
 }

命名空间的定义规则:namespace+空间名称+{}

命名空间namaspace的使用

此时命名空间_240121是一个独立的空间,我们不能直接对该空间内的成员进行访问。

命名空间的使用有三种方式:

1.加命名空名称及作用域限定符

 
/*5 命名空间的使用方法 1.加命名空间名称及作用域限定符*/
#include <stdio.h>
#include <stdlib.h>
namespace _240121 {
    int rand = 10;
 }

int main() {
    printf("%d\n", _240121::rand);
    return 0;
 }

想要使用_240121中的成员时,在成员的前面添加成员所属命名空间的名称以及“::”作用域限定符。此时可以完全避免命名冲突。

此时编辑器直接到_240121命名空间中访问rand成员。

使用规则:命名空间名称+作用域限定符+具体某个成员

2.使用using将命名空间中某个成员引入

 
/*6 命名空间的使用方法 2.使用using将命名空间中某个成员引入
 冲突解决不能用using引入成员,引入之后在外面还是有冲突,
 而是应该直接加命名空间名称以及作用域限定符,直接找到命名空间中的rand*/
#include <stdio.h>
#include <stdlib.h>
namespace _240121 {
    int rand = 10;
 }
using _240121::rand;
int main() {
    printf("%d\n", rand());
    printf("%d\n", rand);
    return 0;
 }

使用using 将_240121命名空间中rand成员引入到全局中,此时rand与stdlib.h中的rand会产生冲突。

 
#include <stdio.h>
#include <stdlib.h>
namespace _240121 {
    int a = 10;
 }
using _240121::a;
int main() {
    printf("%d\n", a);
    return 0;
 }

此时把_240121命名空间中a这个成员引入了,可以直接对a这个成员进行访问。

使用规则:using+命名空间名称+作用域限定符+具体某个成员

3.使用using将namespace命名空间名称引入

 
/*9 命名空间的使用方法 3.使用using namespace 命名空间名称引入*/
#include <stdio.h>
#include <stdlib.h>
namespace _240121 {
    int a = 10;
    int b = 20;
 }
using namespace _240121;
int main() {
    printf("%d\n%d", a, b);
    return 0;
 }

此时把_240121命名空间中所有成员都引入,对所有成员都可以直接访问。

/*8 命名空间的使用方法 3.使用using namespace 命名空间名称引入
 冲突解决不能用using引入命名空间,引入之后在外面还是有冲突,
 而是应该直接加命名空间名称以及作用域限定符,直接找到命名空间中的rand
 综上所述,冲突解决,可以用命名空间解决,但是此时命名空间的引入只能通过,
 加命名空间名称及作用域限定符,来解决
 而不能用using 引入成员或者整个命名空间*/
#include <stdio.h>
#include <stdlib.h>
namespace _240121 {
    int rand = 10;
 }
using namespace _240121;
int main() {
    printf("%d\n", rand());
    printf("%d\n", rand);
    return 0;
 }

冲突解决只能用第一种方式解决,但是平常我们一般不会遇到命名冲突的问题。

使用规则:using namespace+命名空间名称

命名空间的使用习惯

std命名空间的使用惯例:

std是C++标准库的命名空间,如何展开std使用更合理呢?

  1. 在日常练习中,建议直接using namespace std即可,这样就很方便。

  2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对

象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模

大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +

using std::cout展开常用的库对象/类型等方式(第一种方式避免命名冲突)

C++输入输出

输出cout

 
/*10 C++的输出*/
#include <iostream>
using namespace std;//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
int main() {
    int a = 10;
    cout << a << endl;//输出,a数据流向cout,即屏幕
    return 0;
 }

可以理解为cout为屏幕,a变量流向屏幕,即打印a,endl再流向屏幕,即换行。

cout的使用需要使用#include <iostream>头文件

cout是输出,相当于C语言中的printf。

我们会发现,cout打印数据不需要规定他的数据类型,而C语言使用printf打印数据时需要告诉编辑器该打印数据的数据类型是什么,这也是C++的一种优化。

输入cin

 
/*11 C++的输入*/
#include <iostream>
using namespace std;//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
int main() {
    int a;
    cin >> a;//屏幕数据流向a
    cout << a << endl;
    return 0;
 }

可以理解为cin表示屏幕,屏幕输入的数据流向a变量。

cin的使用同样需要使用#include <iostream>头文件

cin是输入,相当于C语言中的scanf。

cin同样不需要规定数据类型。

自动识别数据类型

 
/*12 C++的输入输出可以自动识别数据类型*/
#include <iostream>
using namespace std;//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
int main() {
    int a;
    double b;
    char c;
    cin >> a;//屏幕数据流向a
    cin >> b >> c;
    cout << a << endl;
    cout << b << "  " << c << endl;
    return 0;
 }

cout和cin都可以自动识别数据类型

缺省参数

缺省参数的定义

 
/*13 缺省参数,缺省参数是声明或定义函数时为函数的参数指定一个缺省值。*/
#include <iostream>
using namespace std;//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中

void Func(int a=10){//定义函数时候
    cout<<a<<endl;
 }
int main() {
    Func();
    Func(20);

    return 0;
 }

在函数定义的时候,对参数进行“赋值”,这不是真正的赋值,而是缺省参数的定义。

当这个形参缺少的时候,就是用我们定义的10,如果形参没有缺少,就使用我们传过去的形参值。

缺省参数的分类

  1. 全缺省

  2. 半缺省

1.全缺省

 
/*14 缺省参数,全缺省。*/
#include <iostream>
using namespace std;//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中

void Func(int a = 10,int b = 20, int c = 30) { //定义函数时候
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
 }
int main() {
    Func();

    return 0;
 }

此时Func函数中有三个参数,a,b,c,且这三个参数都是缺省参数。

2.半缺省

 
/*15 缺省参数,半缺省。*/
#include <iostream>
using namespace std;//std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中

void Func(int a ,int b = 20, int c = 30) { //定义函数时候
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
 }
int main() {
    Func(10);

    return 0;
 }

不严谨的说法就是,不是全缺省的就是半缺省。

注意事项

半缺省从右往左依次给出缺省值

需要注意的是半缺省,必须从右往左依次给出缺省值。

 
/*16 缺省参数,半缺省。
 1.半缺省必须从右往左依次给出*/
#include <iostream>
using namespace std;

void Func(int a =10,int b , int c = 30) {
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
 }
int main() {
    Func(10);

    return 0;
 }

半缺省应该分为两个部分,左边部分无缺省值,右边部分有缺省值。

缺省参数不能在函数声明和定义中同时出现

 
/*17 缺省参数。
 1.半缺省必须从右往左依次给出
 2.缺省参数不能在函数声明和定义中同时出现*/
#include <iostream>
using namespace std;

void Func(int a = 10);
void Func(int a = 20) {
    cout << a << endl;
 }
int main() {

    return 0;
 }

此时编辑器不知道使用那一个缺省值。

函数重载

函数重载定义

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这 些同名函数的形参列表(参数个数 类型 类型顺序)不同,常用来处理实现功能类似数据类型 不同的问题。

 
/*18 函数重载。
 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
 不同的问题。*/
#include <iostream>
using namespace std;
//参数类型不同
int add(int left, int right) {
    cout << "int add(int left,int right)" << endl;
    return left + right;
 }
double add(double left, double right) {
    cout << "double add(int left,int right)" << endl;
    return left + right;
 }
//参数个数不同
void f() {
    cout << "void f()" << endl;
 }
void f(int a) {
    cout << "void f(int a)" << endl;
 }
//参数类型顺序不同
void f(int a, char b) {
    cout << "void f(int a, char b)" << endl;
 }
void f(char b, int a) {
    cout << "void f(char b, int a)" << endl;
 }
int main() {

    add(10, 20);
    add(10.1, 20.2);
    f();
    f(10);
    f(10, 'a');
    f('a', 10);
    return 0;
 }

实现函数重载,首先函数名需要相同,其次是,形参列表(参数个数 类型 类型顺序)不同。

需要注意的是,

 
/*18 函数重载。
 函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这
 些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型
 不同的问题。*/
#include <iostream>
using namespace std;

//参数类型顺序不同,不是参数顺序不同
void f(int a, int b) {
    cout << "void f(int a, int b)" << endl;
 }
void f(int b, int a) {
    cout << "void f(int b, int a)" << endl;
 }
int main() {
    f(10, 1);
    f(1, 10);
    return 0;
 }

这样是不构成函数重载的,因为参数个数是一样的,参数的类型和类型顺序也是一样的。

函数重载原理

c:函数名 c++:_Z+函数长度+函数名+类型首字母。

这是C语言和C++对于函数的修饰规则。

C++中:

而C语言中修饰规则是函数名。

引用

引用的定义

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存 间,它和它引用的变量共用同一块内存空间。

 
/*20 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空
 间,它和它引用的变量共用同一块内存空间。
 类型& 引用变量名(对象名) = 引用实体;*/
#include <iostream>
using namespace std;

void testRef(){
    int a=10;
    int& ra=a;
    cout<<a<<endl;
    cout<<ra<<endl;

    cout<<&a<<endl;
    cout<<&ra<<endl;
 }
int main() {
    testRef();
    return 0;
 }

他们的地址是一样的,引用只是给变量取了一个别名,他们共用同一内存空间。

引用的特性

/*21.引用的特性
 1.引用在定义时必须初始化
 2.一个变量可以有多个引用
 3.引用一旦引用一个实体,再不能引用其他实体*/

#include <iostream>
using namespace std;

void test1(){
    int a=10;
    int& ra;
    ra=a;
    cout<<ra<<endl;
    cout<<endl;
 }
void test2(){
    int a=10;
    int& ra=a;
    int& rra=a;
    cout<<ra<<endl;
    cout<<rra<<endl;
    cout<<endl;
 }
void test3(){
    int a=10;
    int& ra=a;
    int b=20;
    ra=b;
    ra=30;
    cout<<ra<<endl;
    cout<<a<<endl;
    cout<<b<<endl;
 }
int main() {
    test1();
    test2();
    test3();
    return 0;
 }

1.引用必须初始化

在test1中,定义int类型的引用但是没有初始化化,系统报错。

2.一个变量可以用多个引用

运行test2得到,

变量a可以有多个引用,也就是可以有多个别名,就像一个人在不同阶段会有其对应的外号,不同的人取的不同的外号,他们都是一个人的别名。

3.引用一旦引用一个实体,就不能引用其他实体

运行test3得到,

我们发现b变量是20,我们没有改变b变量的值。也就是ra=b并不能使得ra变成b的别名。

ra=b的真正含义是b的值赋值匪ra,而ra是a的别名。

引用一旦引用一个实体,就不能引用其他实体。

常引用

用const修饰,表示只读。

 
/*22.常引用 */
#include <iostream>
using namespace std;

void testconstref1() {
    const int a = 10;
    int& ra = a;//该语句编译时会出错,a为常量
}
void testconstref2(){
    const int a = 10;
    const int& ra = a;
    cout << ra << endl << endl;
 }
void testconstref3(){
    int& b = 10;// 该语句编译时会出错,b为常量

    const int& b = 10;

    double d = 3.14;
    int& rd = d;// 该语句编译时会出错,类型不同
}
void testconstref4(){
    double d = 3.14;
    const int& rd = d;//常引用自动类型转换
    const double& rrd=d;
    cout<<rd<<endl<<endl;
    cout<<rrd<<endl<<endl;
 }
int main() {
    testconstref1();
    testconstref2();
    testconstref3();
    testconstref4();
    return 0;
 }

testconstref1中定义了a变量是const只读,此时不能用ra引用a变量,因为如果用ra表示a的别名,此时ra是可读可写,我们用ra改变了a的值,这样就不合理了。

testconstref2中用const修饰的ra引用a,此时ra表示a的别名,且ra是只读的,这样就不会发生通过ra改变只读a的值的情况,所以这个用法是正确的。

testconstref3中b引用常量10,b是可读可写的,但是常量10是只读的,不可以这样使用。

const int& b = 10; 只读的b引用常量10这个用法是正确的。

double d = 3.14; int& rd = d;// 该语句编译时会出错,类型不同

此时rd引用的类型是int而d为double,类型不同会报错。

testconstref4中用常量引用rd表示d的别名,这个用法是可以的,可以理解为只读的引用,可以转换数据类型

引用的使用场景

1.做参数

2.做返回值

 
/*23.引用的使用场景 */
#include <iostream>
using namespace std;

void swap(int& a,int&b){
    int temp=a;
    a=b;
    b=temp;
 }
int& count(){
    static int n=20;//出了作用域还存在
    n++;
    return n;
 }
int main() {
    int a=1,b=10;
    swap(a,b);
    cout<<a<<endl<<b<<endl;

    int& ret=count();
    cout<<ret<<endl;
    return 0;
 }

1.做参数

2.做返回值

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

 
/*24.引用
 如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用
 引用返回,如果已经还给系统了,则必须使用传值返回。 */
#include <iostream>
using namespace std;

int& add(int a, int b) {
    int c = a + b;
    return c;
 }
int main() {
    int& ret = add(1, 2);
    cout << "add(1,2)=" << ret << endl;
    return 0;
 }

程序会崩掉,因为c变量出来作用域就还给系统了,而ret是c的别名,而c已经不存在了。

传值传引用效率比较

1.做函数参数

 
/*25.传值和传引用的效率比较
 作为函数参数*/
#include <iostream>
using namespace std;
#include <time.h>

struct A {
    int a[10000];
 };
void testfunc1(A a) {}

void testfunc2(A& a) {}

void testrefandvalue() {
    A a;

    size_t begin1 = clock();
    for (size_t i = 0; i < 1000000; i++) {
        testfunc1(a);
    }
    size_t end1 = clock();

    size_t begin2 = clock();
    for (size_t i = 0; i < 1000000; i++) {
        testfunc2(a);
    }
    size_t end2 = clock();

    cout << "testfunc1(A a)-->time:" << end1 - begin1 << endl;
    cout << "testfunc2(A a)-->time:" << end2 - begin2 << endl;
 }

int main() {
    testrefandvalue();
    return 0;
 }

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

2.做函数返回值

 
/*26.传值和传引用的效率比较
 作为函数返回值返回*/
#include <iostream>
using namespace std;
#include <time.h>

struct A {
    int a[10000];
 };

A a;

A testfunc1() {
    return a;
 }

A& testfunc2() {
    return a;
 }

void testrefandvalue() {
    size_t begin1 = clock();
    for (size_t i = 0; i < 1000000; i++) {
        testfunc1();
    }
    size_t end1 = clock();

    size_t begin2 = clock();
    for (size_t i = 0; i < 1000000; i++) {
        testfunc2();
    }
    size_t end2 = clock();

    cout << "testfunc1(A a)-->time:" << end1 - begin1 << endl;
    cout << "testfunc2(A a)-->time:" << end2 - begin2 << endl;
 }

int main() {
    testrefandvalue();
    return 0;
 }

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

引用和指针的区别

引用和指针的不同点:

1.引用概念上定义一个变量的别名,指针存储一个变量地址。

2.引用在定义时必须初始化,指针没有要求

3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

4.引用没有NULL引用,但指针有NULL指针

5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

7.有多级指针,但是没有多级引用

8.访问实体方式不同,指针需要显式解引用,引用编译器自己处理

9.引用比指针使用起来相对更安全

内联函数

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

 
/*27.内联函数
 无法声明和定义分离
 空间换时间
 将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不
 是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。*/
#include <iostream>
using namespace std;

inline int add(int left, int right) {
    return left + right;
 }
int main() {
    int ret;
    ret = add(1, 2);
    cout << ret << endl;
    return 0;
 }
  1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会

用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运

行效率。

  1. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建

议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不

是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。

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

了,链接就会找不到。

auto关键字

auto定义

auto作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

 
/*28.auto关键字*/
#include <iostream>
using namespace std;

int testauto(){
    return 10;
 }
int main(){
    int a=10;
    auto b=a;
    auto c='a';
    auto d=testauto();

    cout<<typeid(b).name()<<endl;
    cout<<typeid(c).name()<<endl;
    cout<<typeid(d).name()<<endl;
//    auto e;

    return 0;
 }
 

注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。

auto使用规则

1.auto与指针和引用结合使用

 
/*29.auto关键字的使用规则*/
#include <iostream>
using namespace std;

int main() {
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;

    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;

    *a = 20;
    cout<<x<<endl;
    *b = 30;
    cout<<x<<endl;
    c = 40;
    cout<<x<<endl;

    return 0;
 }

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

2.在同一行定义多个变量

 
/*30.auto关键字的使用规则
 一行的变量数据类型必须是相同的*/
#include <iostream>
using namespace std;

void testauto() {
    auto a = 1, b = 2;
    cout << a << endl << b << endl;

    auto c=3,d=4.0;//报错
    cout<<c<<endl<<d<<endl;
 }
int main() {
    testauto();

    return 0;
 }

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

不能使用auto的场景

 
/*31.auto关键字无法使用的场景*/
#include <iostream>
using namespace std;

void testauto(auto a) {

 }

void testauto() {
    int a[] = {1, 2, 3};
    auto a[] = {1, 2, 3};
 }
int main() {

    return 0;
 }
 

1.auto不能作为函数的参数

2.auto不能直接用来声明数组

范围for

 
/*32.范围for遍历*/
#include <iostream>
using namespace std;


void testfor() {
    int array[] = {1, 2, 3, 4, 5};
    for (auto& e : array) {
        e *= 2;
    }
    for (auto e : array) {
        cout << e << endl;
    }
 }
int main() {
    testfor();
    return 0;
 }
 

for(auto& e:array)表示用e作为第一个元素的别名,接着用e作为第二个元素的别名,一直到e作为最后一个元素的别名为止。

for (auto e : array) 表示用e作为临时变量,其值等于第一个元素的值,接着e作为临时变量,其值等于第二个元素的值。一直到e作为临时变量,其值等于第最后一个元素的值为止。

nullptr空指针

 
/*33.nullptr*/
#include <iostream>
using namespace std;

void f(int) {
    cout << "f(int)" << endl;
 }
void f(int*) {
    cout << "f(int*)" << endl;
 }
int main() {
    f(0);
//    f(NULL);
    f((int*)NULL);
    f(nullptr);
    return 0;
 }

传入Null实际上是想调用f(int*) ,但是Null可能被定义为0或者无类型指针(void*)的常量。

而传nullptr就可以直接调用f(int*) 。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

文章来源:https://blog.csdn.net/m0_74163972/article/details/135751851
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。