主页 > 互联网  > 

C++初阶------------------入门C++

C++初阶------------------入门C++
作者前言

🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂 ​🎂 作者介绍: 🎂🎂 🎂 🎉🎉🎉🎉🎉🎉🎉 🎂 🎂作者id:老秦包你会, 🎂 简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂 喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂 🎂个人主页::小小页面🎂 🎂gitee页面:秦大大🎂 🎂🎂🎂🎂🎂🎂🎂🎂 🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂


C++入门引入 **作者前言**命名空间(namespace):: 命名空间里面嵌套命名空间命名空间的合并 第一个c++代码缺省参数函数重载为啥c++支持函数重载,而C语言不行 引用引用的好处传参返回值常引用 指针和引用的区别内联函数auto范围的for循环指针空值nullptr

命名空间(namespace)

在我们学习C语言的过程中,如果我们命名一些和库函数名字相同的变量或者函数,VS编译器就会报错,怎么解决这个问题呢?C++语言就推出了一个关键字 namespace 这个关键字的作用就是解决命名冲突的 未使用关键字:

#include<stdio.h> #include<stdlib.h> int rand = 0; int main() { printf("%d", rand); return 0; }

是会报错的,因为命名和库函数rand冲突了,我们在后面写的代码越多,就越容易命名冲突, 使用关键字:

#include<stdio.h> #include<stdlib.h> namespace ncon { int rand = 0; } int main() { printf("%d", ncon::rand); return 0; }

::

这个符号叫域作用限定符 就是告诉VS编译器rand这个变量要在ncon命名的空间里面找,否则是找不到这个rand的 在命名空间里面可以定义函数、结构体、变量、枚举…等,

#include<stdio.h> #include<stdlib.h> namespace ncon { int rand = 0; int Add(int a, int b) { return a + b; } typedef struct numnam { int a; int b; }numname; } int main() { printf("%d ", ncon::rand); int count = ncon::Add(1, 2); printf("%d ", count); struct ncon::numnam num = { 10,20 }; printf("%d %d", num.a, num.b); return 0; }

注意一下,结构体的写法是struct关键字在 最前面

命名空间里面嵌套命名空间 #include<stdio.h> #include<stdlib.h> namespace ncon { namespace con { int nums = 10; } } int main() { printf("%d ",ncon::con::nums); return 0; }

命名空间可以嵌套命名空间,无限套娃

命名空间的合并

我们在一个源文件中可以多个位置命名空间相同的名字,是不会冲突的,会合并成一个命名空间 头文件:

#include<stdio.h> #include<stdlib.h> namespace ncon { typedef struct numnam { int a; int b; }numname; }

目标文件.c

#include"day1_1.h" using namespace ncon; namespace ncon { int Add(int a, int b) { return a + b; } } int main() { int count = Add(1, 2); printf("%d ", count); struct ncon::numnam num = { 10,20 }; printf("%d %d ", num.a, num.b); return 0; }

有人就会发现下面这句代码

using namespace ncon;

这句代码想表达的意思就是 这行代码是C++中的语法,意思是引入命名空间 ncon 中的所有内容,使得在代码中可以直接使用该命名空间中的成员而不需要加上前缀 注意:这种方式不提倡,特别是在项目里会造成不必要的麻烦,所以日常练习可以展开 std:是C++官方库定义的命名空间 但是有时候真的很麻烦,会写很多不必要的前缀 所以我们可以指定展开

using std::cout; using std::endl; 第一个c++代码 #include<iostream> int main() { std::cout << "hello world"; printf("hello world"); return 0; }

<< : 流插入

如果要写入一些标识符,如\n

#include<iostream> int main() { std::cout << "hello world\n" << "hello " << "11111 " << "\n"; printf("hello world"); return 0; }

可以写多个 << 进行拼接 但是一般不会这样写,会写成是std::endl

#include<iostream> int main() { std::cout << "hello world" << std::endl << "hello " << "11111 " << std::endl; printf("hello world"); return 0; }

​<< :流提取

#include<iostream> using std::cout; using std::endl; using std::cin; int main() { int a = 10; int b = 20; cin >> a >> b; cout << a << endl << b; return 0; }

std:: cin :输入 std::cout : 输出

缺省参数

我们知道在C语言中,函数的有参数就必须传参,不传参就会报错,为了解决这个问题,c++就有了可以拥有默认参数的函数

#include<iostream> using std::cout; using std::endl; using std::cin; namespace ncon { void Func(int a = 10) { cout << a << endl; } void Func1(int a = 10, int b = 20, int c = 100) { cout << a << endl; cout << b << endl; cout << c << endl; } } using namespace ncon; int main() { ncon::Func(); ncon::Func(30); Func1(); Func1(100); Func1(1,1,1); return 0;

需要注意的是缺省值只能从右往左给,必须是连续给 还有一些是半缺省的函数

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

半缺省参数必须从右往左依次来给出,不能间隔着给

声明和定义不能同时给参数,只能在声明的时候给。这个是一个默认规定 在头文件中

缺省值必须是常量或者全局变量

函数重载

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

#include<iostream> using std::cout; using std::endl; using std::cin; //参数类型不同 int Add(int a, int b) { return a + b; } double Add(double a, double b) { return a + b; } //参数个数不同 void Fun() { cout << "Fun()" << endl; } void Fun(int a) { cout << "Fun(int a)" << endl; } // 参数的顺序不同 void f(int a, double b) { cout << a << endl; cout << b << endl; } void f(double b,int a) { cout << a << endl; cout << b << endl; } int main() { Add(1, 2); Add(1.1, 2.2); Fun(); Fun(6); f(1, 2.2); f(2.2, 6); return 0; }

参数类型不同 参数个数不同 参数的顺序不同

为啥c++支持函数重载,而C语言不行

我们前面学习过C语言的编译链接, 第一步是预处理 :主要进行头文件的展开、宏替换、条件编译(#define 、 #if #endif)以及去掉注释等 生成.i文件 第二步编译:生成汇编代码(主要)或者语法错误 生成.s文件

第三步 汇编:转换成二进制的机器码 生成.o文件(Linux环境下) (在windows是obj文件)

第四步 链接: 合并到一起,链接一些没有确定函数地址、符号表的合并和重定义

C语言不能函数重载的原因:,因为C语言在链接的时候就是使用函数名去找(C语言不存在同名函数),而c++不能使用函数名去找

objdump -S test1c#test1c是一个.out文件

在Linux中C语言函数的调用是通过函数名去找对应的函数找到对应的地址 而在c++ 中

g++ test1.c -o test1cpp objdump -S test1cpp

为啥这里的会这么奇怪,这个函数调用的名字 是由Linux的函数名修饰规则决定的, _Z3Adddd 写法为: _Z + 函数名的字符个数 + 函数名 + 每个参数的第一个字符(int a 就取i)

通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修 饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

引用

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

#include<iostream> using std::cout; using std::endl; using std::cin; int main() { int a = 0; int b = a; int& c = a; c = 2; return 0; } int& c = a;//引用a

int& :引用类型

引用我们可以看作是取别名,改变c或者改变a 都会改变值,而b是一个变量,存储a的值,改变a或者b都不会改变对方

引用的好处 传参

前面我们学习过C语言如果要改变变量的值,要传地址 C语言版:

int exchang(int* a, int* b) { int temp = *a; *a = *b; *b = temp; }

c++版:

int exchang(int& left, int& right) { int temp = left; left = right; right = temp; return left; }

引用的注意:

引用的时候必须初始化引用的指向是唯一的,指定了就不能更改,所以说引用和指针是不能相互取代的一个对象可以有多个引用,但是一个引用不能拥有多个对象引用参数比直接传参的效率要高,引用参数和传地址参数的效率相似 以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直 接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效 率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。 #include<iostream> #include<time.h> using std::cout; using std::endl; using std::cin; void Fun1(int& a) { } void Fun(int a) { } int main() { int a = 12; int b = a; int i = 0; int begin1 = clock(); for(i = 0; i < 1000000; i++) Fun1(a); int end1 = clock(); int begin2 = clock(); for (i = 0; i < 1000000; i++) Fun(a); int end2 = clock(); cout << end1 - begin1 << endl; cout << end2 - begin2 << endl; return 0; }

返回值

在C语言中我们返回一个函数的值的时候,不是返回这个变量

int exchang(int a, int b) { int temp = a; a = *b; b = temp; return a; }

我们知道函数栈帧的创建和销毁,知道返回值是存在寄存器里面返回的

而在c++中

int& exchang(int left, int right) { int temp = left; left = right; right = temp; return left; }

返回的是left的引用,因为left在函数结束的时候就销毁了,所以返回的值是随机值,这样写是错误的写法,前面我们学习过递归的实现,空间是可以重复利用的,

#include<iostream> #include<time.h> using std::cout; using std::endl; using std::cin; int& Fun(int a) { int c = a; c++; return c; } int main() { int& num1 = Fun(6); cout << num1 << endl; cout << num1 << endl; Fun(1000); cout << num1 << endl; return 0; }

这个代码可以看出来,num1引用的是c的原来的地址,函数结束,c销毁,但是那块内存还是存在,每次再调用,就会进行重复利用这块空间

正确的写法: 使用静态变量 前面我们知道如果使用静态变量的话,在静态区创建,而不是在栈区创建,这样就可以在函数销毁的时候静态变量不进行空间的释放

#include<iostream> #include<time.h> using std::cout; using std::endl; using std::cin; int& Fun(int a) { static int c = a; c++; return c; } int main() { int& num1 = Fun(6); cout << num1 << endl; cout << num1 << endl; Fun(1000); cout << num1 << endl; return 0; }

静态变量只初始化一次

某些场景

#include<iostream> #include<time.h> #include<assert.h> using std::cout; using std::endl; using std::cin; typedef struct Seqlist { int a[100]; int size; }SL; void SLModify(SL *ps, int pos, int x) { assert(ps); assert(ps->size > pos); ps->a[pos] = x; } int& SLModify(SL* ps, int pos) { assert(ps); assert(ps->size > pos); return ps->a[pos]; } int main() { SL lis = { {0},100 }; int i = 0; for (i = 0; i < lis.size; i++) { SLModify(&lis, i)++; } return 0; }

这里创建一个顺序表,如果要进行顺序表的每个元素进行加1,有两种方法,要么传地址,要么引用出来

常引用

这里介绍一下

const int a = 10;

这个 在c++中是常量,在C语言中是常变量

#include<iostream> #include<time.h> #include<assert.h> using std::cout; using std::endl; using std::cin; int main() { //引用权限不能放大 const int a = 10; //int& b = a;错误的 const int& b = a; //权限可以缩小 int c = 20; const int& d = c; const int& f = 10; return 0; }

在常数中,如果对常数进行引用,就不会随便的放大权限,常数不能更改,所以对应引用也不能更改,

常性

当我们如果使用使用不同的引用类型去引用一些不同类型的变量 如

int i = 10; double j = i; const double& rj = i;

如果是使用 double&就会报错,为啥? 因为我们在使用不同类型进行接收的时候,i会产生一个临时变量,(类型转变才会产生临时变量)并且这个临时变量具有常性,需要用const的变量进行接收。

指针和引用的区别

引用:

语法上,没有开辟空间,在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。引用比指针使用起来相对更安全没有NULL引用,但有NULL指针有多级指针,但是没有多级引用. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节) #include<iostream> #include<time.h> #include<assert.h> using std::cout; using std::endl; using std::cin; int main() { int c = 20; const int& d = c; cout << sizeof(d); return 0; }

6. 指针语法上,开辟了空间,在底层实现上实际也是有空间的 7. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

两者共同点: 1.引用的底层是汇编实现的

引用表面好像是传值,其本质也是传地址,只是这个工作有编 译器来做

内联函数

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

#include<iostream> using std::cout; using std::endl; using std::cin; int Add(int a, int b) { return a + b; } int main() { Add(1, 2); return 0; }

这里是没有inline修饰,需要创建函数栈帧 使用inline修饰

#include<iostream> using std::cout; using std::endl; using std::cin; inline int Add(int a, int b) { int c = a + b; return c; } int main() { Add(1, 2); return 0; }

未显示展开

要想看到展开我们需要以下操作 内联函数 优点:

可以调试效率高,会展开不用创建栈帧,提高效率

缺点: 不适合于大型的函数,每次调用inline修饰过的函数,就会展开一次,如果函数有100行,调用10000次,合计就要运行100 * 10000行,没有inline修饰的函数,每调用一次,就会找到相同的函数栈帧进行调用,总次数就是 100(函数的行数) + 10000(反汇编的call),所以inline修饰大型函数就会影响可执行程序的大小

inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建 议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不 是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。(简单的理解,看编译器的心情来决定展不展开)

需要注意的是,如果在其他cpp文件使用inline修饰函数,再到头文件声明,在其他cpp文件使用这个函数就会报错,因为使用inline修饰的函数在链接时不会生成符号表。这是因为inline函数在编译时会被直接插入到调用它的地方,而不会产生独立的函数代码。 day1_1.cpp

#include"day1_1.h" int main() { fun(10); return 0; }

day1_2.cpp

#include<iostream> using std::cout; using std::endl; using std::cin; inline void fun(int a) { cout << a << endl; }

day_1.h

#include<iostream> using std::cout; using std::endl; using std::cin; inline void fun(int a);

所以我们使用inline修饰函数,在对当前cpp文件或者在头文件定义和声明就行了

auto

auto关键字可以用于自动推导变量的类型,让编译器根据变量的初始化表达式推导出其类型,从而简化代码书写

#include<iostream> using std::cout; using std::endl; using std::cin; int main() { int a = 10; int* b = NULL; auto c = a; auto& d = a; cout << typeid(a).name() << endl; cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; cout << typeid(d).name() << endl; return 0; }

这里typeid(a).name()是返回a的类型

我们可以得出结论

auto 必须初始化auto 不能当函数参数,返回值也不行auto不能声明数组 范围的for循环

与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。 前面我们学习过C语言的for循环语句,

int i = 0; for (i = 0; i < 100; i++) { printf("%d ", i); }

但是在c++中的for语句有点差别

#include<iostream> using std::cout; using std::endl; using std::cin; int main() { int a[] = { 1,2,3,4,5,6,7,8,9 }; for (auto e : a) { cout << e << " "; } return 0; }

意思就是遍历一遍a数组,每个元素都依次赋值给e,自动判断结束, 修改e不会修改里面的元素

如果我们要修改元素的值

#include<iostream> using std::cout; using std::endl; using std::cin; int main() { int a[] = { 1,2,3,4,5,6,7,8,9 }; for (auto& e : a) { e++; cout << e << " "; } return 0; }

我们可以使用引用来进行修改

指针空值nullptr #include<iostream> using std::cout; using std::endl; using std::cin; void func(int) { cout << "f(int)" << endl; } void func(int*) { cout << "f(int*)" << endl; } int main() { func(0); func(NULL); return 0; }

这里的函数只写了类型,没有写变量,这个在c++是可以的, 看到结果的人可能会发现,为啥都打印了f(int),NULL不是指针类型吗, 其实不是,在c++ NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码: 如果是c++就是把NULL定义成了一个宏,C语言就是一个指针类型

所以c++就为了弥补这个错误,就写出了一个nullptr来代表NULL空指针 注意:

在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入 的。在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
标签:

C++初阶------------------入门C++由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“C++初阶------------------入门C++