一,在c语言的基础上学习C++
参考:比特学习资料;菜鸟教程;各位大神的csdn博客
https://blog.youkuaiyun.com/weixin_61437787?type=blog
https://www.runoob.com/
https://gitee.com/bithange
这是c++之父:本贾尼·斯特劳斯特卢普
起源:到了现代问题越来越复杂,越来越需要高度的抽象和建模,计算机界提出了OOP(object oriented programming)
当已经有了,c语言基础的时候学习c++那么就会容易很多。
先来写一个hello world!
#include<iostream>//io文件流
using namespace std;//全局展开std命名空间
int main()
{
cout<<"hello world!"<<endl;
}
这里我们来讲一下c++的一些特性:
一,命名空间:
在使用c语言的时候,定义的名字可能和库函数起冲突,在项目中多人协作起名字会出现冲突。
1.命名空间:
//一,命名空间
//1.正常命名空间
namespace bit
{
int rand = 10;
}
//2.命名空间的嵌套
namespace n
{
int c;
namespace n1
{
int c;
}
}
//3.多个相同名称的命名空间
//编译器会合并
namespace bit
{
int rand1 = 101;
}
namespace bit
{
int rand2 = 101;
}
2.展开方式
using namespace std;//全局展开std命名空间
2.1全局展开
using namespace bit;
2.2部分展开
using bit::rand;
2.3域作用限定符
int main()
{
printf("%d\n", bit::rand);
return 0;
}
2.4总结:三种方式各有好坏,使用场景有所不同。
-
当我们日常写程序的时候,可以用全局展开
-
协作办公,使用部分展开+与作用限定符
二,输入和输出
#include<iostream>
using namespace std;//std是c++标准库的命名空间名,c++将标准库定义实现都放到这个命名空间中了
int main()
{
cout << "hello world!!!" << endl;
return 0;
}
-
1.使用cout和cin必须**(1)**包含
**(2)**按命名空间使用方式使用std(因为cout和endl和cin都是包含在了std里面)
-
2.endl是特殊的c++符号,表示换行输出
-
3.<<是流插入运算符 >>是流提取运算符,c++可以自动识别变量类型
-
4.c++文件头文件不带h
-
5.实际项目开发中可以这样using std::cout展开常用的库对象/类型方法
三,缺省参数
缺省参数的存在价值:
在函数声明时,为形参设定初始值:
1.当有实参传入时,使用实参。
2.没有实参传入时,则使用初始值。
#include<iostream> //IO流头文件
using namespace std; //全局展开std命名空间
//在函数声明时给形参设定初始值
void print(int val = 999)
{
if (val == 999)
cout << "缺省参数已启用 val 值为:";
else
cout << "缺省参数未启用 val 值为:";
cout << val << endl;
}
int main()
{
print(100);
print();//设有缺省参数的函数,可以不传参数
return 0;
}
实际运用场景示例:
在栈初始化时,设定缺省参数值为4,默认大小为4,
若用户不传参数,则按4来初始化栈的大小
用户传递参数,则按照实参来初始化栈的大小
缺省参数的分类
2.1全缺省参数
void Func(int a = 0,int b=10,int c=100)
{
cout << a << endl;
cout << b << endl;
cout << "c=" << c << endl;
}
2.2半缺省参数
-
只能从右往左给出,不能进行间隔给出
-
缺省参数不能同时在函数和生命中出现,所以只用在函数上就好了
void Func1(int a, int b = 20, int c=50)
{
cout << a << endl;
cout << b << endl;
cout << “c=” << c << endl;
}
//test.h
//声明时缺省
void test(int a = 10);
//test.c
//定义时不必再缺省
void test(int a)
{
cout << a << endl;
}
注意:缺省参数值只能为全局变量或者静态变量
四,函数重载
1.什么是函数重载
函数的一种特殊情况,
c++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数/参数类型/类型的顺序)
我们来看一下Linux环境下objdump -S 可执行程序:
注意:返回值不同不构成函数重载的情况。
2.分类
1.参数的类型不同
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;
}
2.参数的个数不同
void f()
{
cout << "f(没有参数)" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();//f(没有参数)
f(1);//f(int a)
return 0;
}
3.参数的类型顺序不同
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;
}
五,引用
1.何为引用:给变量取一个别名叫做引用
我们会发现:其实引用的底层实现仍然是指针
可以简单理解引用是智能版指针,会自动解引用
2.规则与特性:
-
引用类型必须和引用实体是同种类型的
-
引用在定义的时候必须进行初始化
-
一个变量可以被多个别名来引用。
-
引用一旦确立,就不能再引用别的实体了。
int main()
{
int a = 10;
int b = 50;
//int& ra;//1.报错:没有确立引用
//2.一个变量被多个引用
int& ra = a;
int& ta = a;//3.每个引用一生只为一个人 printf("%p\n", (void*)&ra);//000000B8719BF7E4 printf("%p\n", &ra);//000000B8719BF7E4 printf("%d\n\n", ra);//10 ra = b;//没问题,实际上让a=b=50了,是一个赋值操作 printf("%d\n", a);//50 printf("%p\n", &ra);//000000B8719BF7E4 printf("%d\n", ra);//50 //错误做法: //int& ra = b;//想引用别的 //4.不存在多级引用 int& ya = a; //int&& rra = a;//非法 int& rra = ya;//合法实际结果为 int& rra=a; //继续思考这个当引用rra代表ya时,实际上就代表引用ya所代表的变量a
}
3.常引用:
对于指针和引用来说,存在权限的问题,因为指针和引用都具有直接修改原数据的能力
程序中存在几个区域:栈,堆,静态区等
我们使用的常量位于数据段或者代码段中,具有可读不可修改性,当我们使用普通指针或者引用常量数据时或报错。
3.1 权限不可放大
如下:
解决:将指针或者引用改为只读权限,就可以了
int main()
{
//1.权限不能放大
const int a = 10;
//int& b = a;报错,这样就是能修改了
const int& b = a;//合法,权限和原来一样
//2.权限可以进行缩小
int c = 20;
const int& d = c;
//10本身就是常量,所以只能常引用
const int& e = 10;
//int& y = 101;//无法从int转换为int&
}
4.使用方法:
4.1做参数
void swap(int& ra, int& rb)
{
//有了引用之后,不需要再解引用,也能达到指针的效果
int tmp = ra;
ra = rb;
rb = tmp;
}
4.2做返回值
#include<iostream>
using namespace std;
int arr[10] = {0}; //数组为全局变量
int& getVal(int pos)
{
//返回数组指定位置的值
return arr[pos];
}
int main()
{
for(int i = 0; i < 10; i++)
{
//借助引用,可以直接通过返回值修改数组中的值
getVal(i) = i * 10;
//相当于arr[pos]=i*10
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
4.2.1
引用返回很强大,但也不能随便使用,
引用返回一般用于生命周期较长的变量,就是函数结束后不会被销毁的变量吗。
生命周期短的变量作为引用返回值,那么结果将是未定义的。(依照编译器)
int& func(int n)
{
int val = n;
return val; //结果未定义
}
//val是函数 func 中的局部变量,当函数结束后,变量就被销毁了
//此时可能得到正确的结果(编译器未清理),也可能得到错误的结果(编译器已清理)
//因此说结果是未定义的
//可以看到下图中相同语句出现两种结果
5.效率比较:
5.1 传值和传引用
#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 < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
5.2 作为值和作为引用返回类型的性能比较
#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;//143
cout << "TestFunc2 time:" << end2 - begin2 << endl;//1
}
int main()
{
TestReturnByRefOrValue();
return 0;
}
6.引用和指针的区别:
1.语法概念:引用是一个别名,没有独立的空间和引用实体共用一块空间。
2.底层实现:实际是有空间的,因为引用时按照指针的方式来实现的。
#include <iostream>
using namespace std;
int main()
{
char a = 'a';
char& ra = a;
cout << "&a = " << (void*) (& a) << endl;//tip:%p通常用于打印void*类型的指针,将&a转化为void*可以保证输出的是指针的地址
cout << "&ra = " << (void*) (&ra) << endl;
//上面的两个一个是&a 另一个是&ra ,都是a和ra的地址
cout << sizeof(ra) << endl;//1 返回的是ra的类型
return 0;
}
引用返回的原理:
7.涉及需要改变原变量值时,优先考虑使用引用,特殊场景下,仍然需要使用指针
引用与指针互不冲突,两者都有自己适用的领域**,比如在实现链表时,必须使用指针,因为****引用无法改变指向,而链表需要频繁的进行改链操作。**
六,内联函数
存在价值:
内联函数主要是为了替代宏函数,因为它存在很多坑,并且某些使用场景比较复杂。
宏的缺点:1.没有类型的安全检查 2.不能进行调试,直接替换
#define ADD(x, y) ((x) + (y)) //通过宏函数实现ADD,比较复杂、麻烦
2.实现:
内联函数就是函数实现前加上inline来修饰,此时函数会被编译器标记为内联函数。
内联函数的特点:
-
在debug模式下,函数不会进行替换,可以调试
-
在release模式下,函数如宏函数一样展开,提高程序运行速度
inline int ADD(int x, int y)
{
int ret = x + y;
return ret;
}
int main()
{
cout<<ADD(1,2)<<endl;
return 0;
}
3.内联函数使用注意事项:
-
使用内联函数多的时候,编译出来的程序会更大,代码会变多,但运行速度变快。
-
调用内联函数时,编译器可能会展开也可能不会,这取决于编译器是否会觉得展开后影响性能
-
内联函数适用于代码行数比较少,且被频繁调用的小函数
-
内联函数推荐在声明的时候顺便把函数定义,如下:
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_Hinline int add(int a, int b) {
return a + b;
}#endif
// main.cpp
#include
#include “math_utils.h”int main() {
int result = add(5, 7);
std::cout << "Result: " << result << std::endl;
return 0;
}
关于第四点有如下进一步解释:
当你将内联函数的定义放在单独的源文件中而没有在头文件中展开,可能会导致链接错误,因为编译器无法在链接阶段找到该函数的定义。以下是一个示例来说明这个问题:
假设你有一个名为math\_utils.h
的头文件,其中包含了一个内联函数的声明:
cppCopy code// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
inline int add(int a, int b);
#endif
然后,你创建了一个名为math\_utils.cpp
的源文件,其中包含了内联函数的定义:
cppCopy code// math_utils.cpp
#include "math_utils.h"
inline int add(int a, int b) {
return a + b;
}
但是,在你的主程序文件(例如main.cpp
)中,你没有包含math\_utils.cpp
,只包含了头文件math\_utils.h
:
cppCopy code// main.cpp
#include <iostream>
#include "math_utils.h"
int main() {
int result = add(5, 7);
std::cout << "Result: " << result << std::endl;
return 0;
}
在这种情况下,当你尝试编译这些文件并进行链接时,可能会出现链接错误,因为编译器无法找到add
函数的定义。这是因为math\_utils.cpp
中的函数定义没有被包含在main.cpp
中,而内联函数的定义需要在编译单元(源文件)中可见。
为了解决这个问题,你可以选择将math\_utils.cpp
编译到同一个可执行文件中,或者在main.cpp
中包含math\_utils.cpp
的源文件,或者将内联函数的定义放在头文件math\_utils.h
中,以便在包含头文件时,编译器可以看到函数定义。这样,编译器就可以在链接阶段正确找到内联函数的定义,避免链接错误。
七,auto关键字
auto关键字可以自动识别目标变量的类型,自动转换成相应的类型
1.识别并转换
int a = 10;
int* b = &a;
auto aa = a; //此时 aa 为 int
auto bb = b; //此时 bb 为 int*
2.转换为指定类型
int a = 10;
auto* pa = a; //指定 pa 为 int*
auto& ra = a; //指定 pa 为 int&
3.一行声明多个变量
auto a = 1, b = 2, c = 3; //合法,类型统一
auto a = 1, b = 2.2; //非法,类型不统一
pay attention:auto不能用于数组,也不能当作参数的类型(也就是函数的参数不能包含auto)
八,范围for
#include<iostream>
using namespace std;
int main()
{
int arr[] = { 9,2,3,4,5 };
//范围for循环
//拥有自动拷贝的超能,自动判断范围的能力,自动结束的特点
for (auto val : arr)
{
cout << val << ' ';
}
cout << endl;
return 0;
}
#include<iostream>
using namespace std;
int main()
{
int arr[] = {8,6,2,5,9};
for (auto &val : arr)
{
val *= 2;
cout << val << ' ';
}
cout << endl;
return 0;
}
九,nullptr
在C++中,指针空值被定义为了0,而不是void*
所以就出现了nullptr这个**关键字,**其大小和void*一致
#include<iostream>
using namespace std;
void func(int)
{
cout << "参数为整型 0" << endl;
}
void func(void*)
{
cout << "参数为空指针" << endl;
}
int main()
{
func(0);
func(nullptr);
return 0;
}