c++:入门

1.关键字

在这里插入图片描述

2.命名空间

1.命名冲突问题

#include<stdio.h>
#include<stdlib.h>
int rand = 0;

int main()
{
	scanf("%d", &rand);
	printf("%d", rand);
	return 0;
}

编译会发生错误
在这里插入图片描述
原因: 编译器在编译处理阶段第一步是把.h文件在.cpp里展开,<stdlib.h>就有rand函数。

命名冲突问题:1.自己定义的变量,函数可能跟库里面命名冲突2.同一项目多人协作,与别人写的代码,命名冲突
所以我们用 命名空间来解决这个问题。

如何使用

首先我们先定义了一个叫zly的命名空间

//定义了一个叫zly的命名空间 -- 域
namespace zly
{
	int rand = 0;
}

但此时打印的是stdlib.h里函数rand的地址
所以我们要用到::(域作用限定符

printf("%d\n", zly::rand);

::在左边的域里面找,左边是空白就是全局域

1.命名空间还可以定义变量/函数/类型
#include<stdio.h>
#include<stdlib.h>

//定义了一个叫zly的命名空间 -- 域
namespace zly
{
	int rand = 0;
	int Add(int left, int right)
	{
		return right + right;
	}

	struct Node
	{
		struct Node* next;
		int val;
	};
}
int main()
{
	zly::rand = 10;
	zly::Add(1, 2);
	struct zly::Node node;
	return 0;
}
2.命名空间还可以嵌套定义
namespace zly
{
	int rand = 0;
	int Add(int left, int right)
	{
		return right + right;
	}

	struct Node
	{
		struct Node* next;
		int val;
	};
	namespace cl
	{
		int c = 1;
		int Sub(int left, int right)
		{
			return left - right;
		}
	}
}

调用cl里的函数只需要:

//先到zly里面找到cl的命名空间,再到cl的命名空间里找到Sub
zly::cl::Sub(9, 5);
3.同一工程中允许存在多个相同名称的命名空间,编译器最后会合并到一个命名空间
如果我们想用using namespace zly;全部展开, 以下两点需要注意:

在这里插入图片描述
慎用下面的方法
在这里插入图片描述
不过我们可以单独展开某一个,其他不展开–用于展开命名空间中常用的

using zly::Node;

3.C++输入&输出

我们想打印可以使用:

#include<iostream>
//c++库的实现定义在一个叫std的命名空间中
using namespace std;

int main()
{
	cout << "hello world" << endl;
	return 0;
}

但是这样不是很好(平时练习使用),相当于把std的库全部展开了
可以使用:

#include<iostream>

int main()
{
	std::cout << "hello world" << std::endl;
	return 0;
}

我们也可以把常用的展开

using std::cout;
using std::endl;

int main()
{
	cout << "hello world" << endl;
	return 0;
}

“<<”可以看作流插入,cout可以看作控制台,endl可以看作换行。等价于printf("hello \n")

特点:自动识别类型

using std::cout;
using std::endl;

int main()
{
	cout << "hello world" << endl;
	int i = 10;
	double d = 1.34;
	cout << i << " " << d << endl;
	return 0;
}
cout和printf哪个方便用哪个
当打印结构体的内容时需要用到解释语,printf方便
//cpp用法
	cout << "姓名:" << s.name << endl;
	cout << "年龄:" << s.age << endl<<endl;
	//c语言
	printf("姓名:%s\n年龄:%d\n", s.name, s.age);
cin和scanf,">>"流提取运算符
cin >> s.name >> s.age;
scanf("%s%d", s.name, &s.age);

4.缺省参数

概念:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
全缺省:所以参数都给了缺省值(默认值)

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

int main()
{
	Func();//abc的缺省值不改变
	Func(1);//只传前一个
	Func(1, 2);//只传前两个
	Func(1, 2, 3);//传三个
}

在这里插入图片描述
半缺省:缺省部分参数—必须从右往左缺省,必须连续缺省
像void(int a=10,int b,int c)或者void(int a,int b=10,int c)都是不行的

void Func(int a, int b, int c = 20)
//某个参数必须传,放在前面,不一定要传就放在后面
{
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl<<endl;
}

int main()
{
	Func(1, 2);//没有给缺省值必须要传
	Func(1, 2, 3);
}

缺省参数不能在函数声明和定义中同时出现,要么在声明,要么在定义,推荐写在声明

这是错误的:

//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}

5.函数重载

定义:C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的
形参列表(参数个数 或 类型 或 顺序)必须不同。

//参数类型不同
int Add(int left, int right)
{
    return left + right;
}
double Add(double left, double right)
{
	return left + right;
}
//参数个数不同
void Func(int a, int b)
{
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
}
void Func(int a)
{
	cout<<"a="<< a << endl;
}
//参数顺序不同
void Func(int a, char c)
{
	cout << "a=" << a << endl;
	cout << "c=" << c << endl;
}
void Func(char c,int a)
{
	cout << "a=" << a << endl;
	cout << "c=" << c << endl;
}
注意:1.返回值不同不能作为函数重载的依据 2.缺省值不同,不能构成重载
//返回值不同,调用的时候不能区分
int f(double d)
{

}

void f(double d)
{

}
//缺省值不同
void f(int a)
{
	cout << "a=" << a << endl;
}

void f(int a = 10)
{
	cout << "a=" << a << endl;
}
但是一个带参,一个不带参时,出现了问题
void f()
{
	cout << "f()" << endl;
}

void f(int a=10)
{
	cout << "f(int a)" << endl;
}

int main()
{
	f();//调用存在歧义
	
	return 0;
}
c语言不支持重载,而c++支持,接下来就是分析为什么
编译器编译的过程: func.h func.c test.c

1.预处理 ->头文件展开、宏替换、条件编译、去掉注释

func.i test.i
2.编译 -> 检查语法,生成汇编代码
func.s test.s
3.汇编 ->汇编代码转换成二进制机器码
func.o test.o
4.链接
a.out

c语言不支持重载和c++支持就是链接部分出的问题

c语言不支持函数重载,因为编译的时候,两个重载函数,函数名相同,在func.o符号表中存在歧义和冲突,因为他们都是使用函数名去标识和查找,而重载函数函数名相同
c语言:

在这里插入图片描述

c++的目标文件符号表中不是直接用函数名来标识和查找函数

1.函数名修饰规则,但是这个1修饰规则,不同的编译器下不同
Linux: -Z+函数名长度+函数名+参数首字母
在这里插入图片描述

2.有了函数名修饰规则,只要参数不同,func.o的符号表就不存在二义性和冲突了。
在这里插入图片描述

3.链接的时候,test.o的main的函数里面调用两个重载的函数,查找地址时,也是明确的
在这里插入图片描述
在这里插入图片描述

main 函数:
在这里插入图片描述
而c语言对函数名的处理:

在这里插入图片描述

6.引用

概念: 引用不是新定义一个变量,而是给已存在变量取了一个别名,它和已存在的变量共用一块空间

表达方式: 类型& 引用变量名=引用实体

比如:int a = 10; int& b = a;
在这里插入图片描述
引用在语法层,没有开辟新空间,而是对原来的空间取新名称,跟人的大名和绰号一样
引用的特性:
1.引用必须在定义时初始化在这里插入图片描述
2.一个变量可以有多个引用

	int a = 10;
	int& b = a;
	int& c = b;
	int& d = c;

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

	int a = 10;
	int& b = a;
	int c = 10;
	//b不能变成c的别名
	int& b = c;
    //c的值不能赋给b
	b = c;

应用

1.引用作为参数

最简单的交换函数:我们知道形参的改变不会影响实参,所以要想改变实参就要传实参的地址,但是传引用也能实现

void Swap(int a, int b)//声明//传值
{
	int tmp = a;
	a = b;
	b = tmp;
}

void Swap(int& r1, int& r2)//既是声明也定义了//传址
{
	int tmp = r1;
	r1 = r2;
	r2 = tmp;
}

void Swap(int* p1, int* p2)//传引用
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

这三个函数构成重载(参数的类型不同),但是Swap(a,b)调用的时候会发生歧义,不知道调用传值还是传引用。

用引用解决单链表中传二级指针问题
原先我们想要进行单链表的尾插,需要传二级指针,也就是指针plist的地址
int main()
{
	SL* plist = NULL;
	SeqListPushBack(&plist, 1);
	SeqListPushBack(&plist, 2);
	SeqListPrint(plist);
	return 0;
}

void SeqListPushBack(SL** pphead, STDataType x)
{
	SL* newnode = CreatListNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SL* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

首先我们要理解,int 类型可以有别名,int* 同样也可以有,所以以下两种写法是对的

    int a = 10;
	int& b = a;

	int* p1 = &a;
	int*& p2 = p1;

所以单链表的尾插同样可以通过传引用实现
phead是plist的别名,phead的改变就是plist的改变

int main()
{
	SL* plist = NULL;
	SeqListPushBack(plist, 1);
	SeqListPushBack(plist, 2);
	SeqListPushBack(plist, 3);
	SeqListPrint(plist);
	return 0;
}

void SeqListPushBack(SL*& phead, STDataType x)
//phead是plist的别名,phead的改变就是plist的改变
{
	SL* newnode = CreatListNode(x);
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		SL* tail = phead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

2.引用作返回值
就拿Add函数举例

传值返回
int Add(int a, int b)
{
	int c = a + b;
	return c;
}

c不是真正的返回值,而是临时变量
c是局部变量,返回给ret的时候栈帧已经销毁,再取c就会非法访问

临时变量存在哪?
如果c比较小(4/8字节),一般寄存器充当临时变量
如果c比较大,临时变量存放在调用函数的栈帧中

所以我们要知道:所有的传值返回都会生成一个拷贝

传引用返回:不会生成c的拷贝返回,而是返回c的引用
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

代码的问题
1.存在非法访问,因为Add函数的返回值是c的引用,所以Add栈帧销毁以后,会去访问c位置的空间
2.如果Add函数栈帧销毁,清理空间(有可能会清理),那么取c值的时候就是随机值,给ret的就是随机值,取决于编译器

主函数这么调用时会发生问题:

int main()
{
	int& ret = Add(1, 2);
	cout << "ret:"<<ret << endl;
	Add(10, 20);
	cout <<"ret:"<< ret << endl;
	return 0;
}

在这里插入图片描述
ret值改变了因为ret就是c的引用()
当cout函数占用了销毁了的空间,对c覆盖了,就会发生下面问题

int main()
{
	int& ret = Add(1, 2);
	cout << "ret:"<<ret << endl;
	cout << "111111111111111111111"<< endl;
	cout <<"ret:"<< ret << endl;
	return 0;
}

在这里插入图片描述
所以一般不用传引用返回,所以什么情况能用传引用返回呢?
如果函数返回时,出了函数作用域,如果返回对象还在,可以用传引用返回,如果已经还给系统了,必须使用传值返回
比如:静态变量,全局变量,malloc出来的

//n是静态变量就可以使用传引用返回了
int& Add(int a,int b)
{
    static int n=0;
    n++;
    return n
}

arr是全局变量

#include <time.h>
struct A{ int a[10000]; };

A a;
// 值返回 -- 每次拷贝40000byte
A TestFunc1() { return a; }

// 引用返回 -- 没有拷贝
A& TestFunc2(){ return a; }

或者下面出了作用域还存在

char& at(char* str,int i)
{
    return str[i];//出了作用域,str[i]没有还给系统
}

int main()
{
  char arr[10];
  at(arr,3);
  return 0;
}

malloc出来的
p不能返回,*p可以返回

int*& f()
{
   int* p=(int*)malloc(4);
   return *p;
}
引用的作用主要体现在传参和传返回值 1.引用传参和传返回值,有些场景可以提高性能 2.作输出型参数和输出型返回值。
引用还有一个概念: 常引用

权限放大(不可以)
const int a; a都是只读的,不能修改
在这里插入图片描述
权限不变(可以)

	const int a = 10;
	const int& b = a;

权限缩小(可以)

    int c = 10;
	const int& d = c;

在c语言中学过了类型转换

在这里插入图片描述

因为在类型转换的过程中,创建了临时变量,d先把整型部分给临时变量,临时变量再给i,临时变量具有常性,是右值,不可修改。用int& 来接收就发生错误,加个const就能避免错误.

右值:通常是不能被修改的,比如:表达式产生的临时变量,常量

结论:const Type& 可以接收各种类型

引用的价值

1.作参数–提高效率

2.作返回值(不要返回局部变量的引用)意义:修改返回对象
在这里插入图片描述

传值返回,会拷贝给临时对象,临时对象作返回值。 传引用返回,返回的是返回对象的引用(别名)

价值:a.提升效率 b.修改返回变量
修改返回变量

int& At(int i)//可读可写
{
	static int a[N];
	return a[i];
}

int main()
{
	for (int i = 0; i < N; i++)
	{
		At(i) = 10 + i;//写操作
	}
	for (int i = 0; i < N; i++)
	{
		cout << At(i) << "" ;//读操作
	}
	cout << endl;
	return 0;
}

如果去掉引用就会报错:

int At(int i)
{
	static int a[N];
	return a[i];
}

在这里插入图片描述

因为:临时变量具有常性,是右值(可读,但不能修改)
返回的不是a[i]了而是a[i]拷贝的临时变量

指针跟引用的区别

按自己的理解:1.指针是存储地址,引用是一个变量的别名。2.引用必须初始化,指针最好初始化。3.引用了一个实体后不能引用其他实体4.sizeof不同,sizeof(指针)==4个字节,sizeof(引用)==引用类型的大小

使用指针要考虑空指针,野指针等问题,指针太灵活了,相对而言,没有引用安全

语法层:指针和引用是完全不同的概念;指针是开空间,存储变量地址;引用不开空间,仅仅是对变量取别名

7.extern c

步骤参考下面博客

extern c博客

8.内联函数

定义一个Add函数,如果我们想多次调用

调用函数需要建立栈帧,栈帧中要保存一些寄存器,结束后要恢复
每次调用都会有消耗

在C语言中,我们可以用宏来替换Add函数,直接替换,没有消耗

#define Add(x,y) ((x)+(y))
//不加“;”,并且注意()

C++给了新的语法——内联函数

inline int Add(int x, int y)
{
	int ret = x + y;
	return ret;
}

反汇编之后,可以看到,内联函数直接展开,不是内联的还得调用
在这里插入图片描述
inline的特性:
1.inline是以空间换时间的做法,省去调用函数的额外开销。所以代码很长或者有递归,就不适合作为内联函数,调用的地方很多,展开后程序会变大
在这里插入图片描述
2. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到,想用的话直接定义时内联

// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?
f@@YAXH@Z),该符号在函数 _main 中被引用

f.cpp->f.o 符号表中不会生成f函数的地址
因为inline函数是不需要地址的,都在调用的地方展开了

结论:短小,频繁调用的函数建议定义成inline

auto关键字

    const int a = 10;
	//自动推导c的类型
	auto c = a;//c的类型是int 
	//const int a=10;
	//auto c = &a;//c的类型是int const*
	auto d = 'a';//d是char类型
	auto e = 10.22;//e是double类型

auto 只要类型,const的属性会丢失;auto 不能独立定义,比如auto a;,必须给个值,因此auto不能作为参数;auto 也不能定义数组

在这里插入图片描述

auto的价值体现:语法糖:范围for

遍历数组,原先和c++11的对比

    int sz = sizeof(arr) / sizeof(arr[1]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		cout << arr[i] << endl;
	}
	// c++11 范围for
	//会自动依次取数组里面的每个元素,依次赋值给e,也会自动判断结束
	for (auto e : arr)
	{
		cout << e << endl;
	}
	//可以用auto,也可以写清楚类型
	for(int x : arr)
	{
	   cout<< x <<endl;
	}

在范围for 的基础上,如何把数组中每个值+1?

   //把数组中每个值+1
	for (auto e : arr)
	{
		e++;//这个方法不对,数组会把值赋给e,e++,并不会改变数组
	}
	//这个方法可以,数组里的每个值都赋值给e,e又是每个值的别名,e++,数组里每个值也++
	//e的生命周期不是整个循环,而是一次循环
	for (auto& e : arr)
	{
		e++;
	}

指针空值nullptr

void f(int )
{
	cout << "f(int)" << endl;
}

void f(int* )
{
	cout << "f(int*)" << endl;
}

int main()
{
	//c++98
	int* p1 = NULL;
	int* p2 = 0;

	// c++11
	int* p3 = nullptr;
	f(NULL);//匹配 f(int );原因是:c++中通过宏把NULL替换成0
	f(0);
	f(nullptr);//f(int* );c++中通过宏把nullprt替换成void* 0
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Typora是一款适合写一些程序相关的博客,因为可以用代码+快捷键的方式,方便的对文章内容进行处理,不用在担心排版和样式变形的问题了,在编辑的过程中,就能预览到效果,看到这篇文章,能让在短时间内适应Typroa的使用,一起了解一下吧! Typora介绍:Typora是一款适合写一些程序相关的博客,因为可以用代码+快捷键的方式,方便的对文章内容进行处理,不用在担心排版和样式变形的问题了,在编辑的过程中,就能预览到效果,看到这篇文章,能让在短时间内适应Typroa的使用,一起了解一下吧! ## 1标题设置 “#” + “空格” +“标题” {^"#"的个数表明几级标题} #一级标题 ##二级标题 ###三级标题 **6个#就是六级标题** 或ctrl+“数字键”{^数字是几即标题几级} Ctrl+1 一级标题 Ctrl+2 二级标题 Ctrl+3 三级标题 Ctrl加数字6就是六级标题 # 2序列 ## 2.1有序序列 “1.” + “空格” +“文字内容” 有序序列 按下换行键,第二行的排头自动出现 双击换行键,可退出序列 ## 2.2创建无序序列 “*“或”+“或”-” + “空格” + “文字内容” 无序序列 按下换行键,自动出现 双击换行键,可退出序列 ## 2.3创建可选序列 “*“或”+“或”-” + “空格” +[ ] + “空格” + “文字”{^注意,中括号内,有空格} # 3代码块 “~~~” + 使用语言的名字 ![2020-5-18 19-59-45](C:\Users\Tao\Desktop\CSDN\照片\2020-5-18 19-59-45.jpg) # 4引注 “>” + “空格”+“引注内容” # 5表格 Ctrl + T 就可以出现表格,可以一有的选择行数,也可以的选择字体位置 ![2](C:\Users\Tao\Desktop\CSDN\照片\2.jpg) # 6数字块 “$$” + “enter”就会出现输入区域($符号为英文模式下shift+4) ![3](C:\Users\Tao\Desktop\CSDN\照片\3.jpg) # 7水平线 “***“或”—” + “换行键” 效果如下 # 8脚注 { "^" + “ 注释内容 ” } # 9删除线 \~\~删除线的内容\~\~ 效果:~~删除内容~~ # 10字体加粗 使用 两个星号 或者 两个下划线 可以字体加粗,快捷键 Ctrl + B 11字体倾斜 使用 单个星号 或者 单下划线 可以倾斜字体。快捷键 Ctrl + I # 12图片的插入 {显示的文字](C:\Users\Hider\Desktop\echart.png "图片标题") {显示的文字](C:\Users\Hider\Desktop\echart.png) 还可以直接将图片拖拽进来,自动生成链接 # 13URL Typora允许用, 把URL作为链接插入。 Typora还会自动链接标准网址。 www.baidu.com ## Typora快捷键 | 快捷键 | 作用 | 快捷键 | 作用 | | :---------- | :----------------- | :----------- | :------------- | | Ctrl+1 | 一阶标题 | Ctrl+B | 字体加粗 | | Ctrl+2 | 二阶标题 | Ctrl+I | 字体倾斜 | | Ctrl+3 | 三阶标题 | Ctrl+U | 下划线 | | Ctrl+4 | 四阶标题 | Ctrl+Home | 返回Typora顶部 | | Ctrl+5 | 五阶标题 | Ctrl+End | 返回Typora底部 | | Ctrl+6 | 六阶标题 | Ctrl+T | 创建表格 | | Ctrl+L | 选中某句话 | Ctrl+K | 创建超链接 | | Ctrl+D | 选中某个单词 | Ctrl+F | 搜索 | | Ctrl+E | 选中相同格式的文字 | Ctrl+H | 搜索并替换 | | Alt+Shift+5 | 删除线 | Ctrl+Shift+I | 插入图片 | 注:一些实体符号在使用之后要加空格,还有一些实体符号需要在实体符号之前加”\”才能够显示。如有错误,还挺谅解。 tyopra下载链接:https://www.typora.io/ 参考链接1:[Typora入门(中文版)](https://www.simon96.online/2018/10/18/Typora入门(中文版)/) 参考链接2:[Typora](
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值