一,在c语言的基础上学习C++

一,在c语言的基础上学习C++

参考:比特学习资料;菜鸟教程;各位大神的csdn博客
https://blog.csdn.net/weixin_61437787?type=blog
https://www.runoob.com/
https://gitee.com/bithange
image

这是c++之父:本贾尼·斯特劳斯特卢普

image

起源:到了现代问题越来越复杂,越来越需要高度的抽象和建模,计算机界提出了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. 1.使用cout和cin必须**(1)**包含

                                 **(2)**按命名空间使用方式使用std(因为cout和endl和cin都是包含在了std里面)
    
  2. 2.endl是特殊的c++符号,表示换行输出

  3. 3.<<是流插入运算符 >>是流提取运算符,c++可以自动识别变量类型

  4. 4.c++文件头文件不带h

  5. 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来初始化栈的大小

用户传递参数,则按照实参来初始化栈的大小

image

缺省参数的分类

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半缺省参数

  1. 只能从右往左给出,不能进行间隔给出

  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 可执行程序:

image

注意:返回值不同不构成函数重载的情况。

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.何为引用:给变量取一个别名叫做引用

我们会发现:其实引用的底层实现仍然是指针

image

可以简单理解引用是智能版指针,会自动解引用

2.规则与特性:

  1. 引用类型必须和引用实体是同种类型的

  2. 引用在定义的时候必须进行初始化

  3. 一个变量可以被多个别名引用

  4. 引用一旦确立,就不能再引用别的实体了。

    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 权限不可放大

如下:

image

解决:将指针或者引用改为只读权限,就可以了

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;
}

image

4.2.1

引用返回很强大,但也不能随便使用,

引用返回一般用于生命周期较长的变量,就是函数结束后不会被销毁的变量吗。

生命周期短的变量作为引用返回值,那么结果将是未定义的。(依照编译器)

int& func(int n)
{
	int val = n;
	return val;	//结果未定义
}

//val是函数 func 中的局部变量,当函数结束后,变量就被销毁了
//此时可能得到正确的结果(编译器未清理),也可能得到错误的结果(编译器已清理)
//因此说结果是未定义的
//可以看到下图中相同语句出现两种结果

image

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;
}

引用返回的原理:

image

7.涉及需要改变原变量值时,优先考虑使用引用,特殊场景下,仍然需要使用指针

引用指针互不冲突,两者都有自己适用的领域**,比如在实现链表时,必须使用指针,因为****引用无法改变指向,而链表需要频繁的进行改链操作。**

六,内联函数

存在价值:

内联函数主要是为了替代宏函数,因为它存在很多坑,并且某些使用场景比较复杂。

宏的缺点:1.没有类型的安全检查 2.不能进行调试,直接替换

#define ADD(x, y) ((x) + (y))	//通过宏函数实现ADD,比较复杂、麻烦

2.实现:

内联函数就是函数实现前加上inline来修饰,此时函数会被编译器标记为内联函数。

内联函数的特点:

  • 在debug模式下,函数不会进行替换,可以调试

  • 在release模式下,函数如宏函数一样展开,提高程序运行速度

image

inline int ADD(int x, int y)
{
	int ret = x + y;
	return ret;
}
int main()
{
	cout<<ADD(1,2)<<endl;
	return 0;
}

3.内联函数使用注意事项:

  1. 使用内联函数多的时候,编译出来的程序会更大,代码会变多,但运行速度变快。

  2. 调用内联函数时,编译器可能会展开也可能不会,这取决于编译器是否会觉得展开后影响性能

  3. 内联函数适用于代码行数比较少,且被频繁调用的小函数

  4. 内联函数推荐在声明的时候顺便把函数定义,如下:

    // math_utils.h
    #ifndef MATH_UTILS_H
    #define MATH_UTILS_H

    inline 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;
}

image

九,nullptr

在C++中,指针空值被定义为了0,而不是void*

image

所以就出现了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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值