c++入门

文章目录

命名空间

解决c语言命名冲突问题
1.我们自己定义的变量,函数可能和库里面重名冲突
2.但是进入公司项目组里面,做的项目通常比较大,多人协助,会导致代码中命名冲突 c语言无法解决这个问题,除非换名字 c++提出了新语法,叫命名空间,
3.命名空间里面可以包含各种东西,函数,结构体,变量

1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
#include<stdlib.h>
int rand = 0;

int main()
{
printf("%d", rand);//如果没加stdlib.h这个头文件的化,
//是可以正常运行的,但是如果包了这个头文件时,rand就是里面的函数
//因为在预处理的时候,头文件展开,里面有rand这函数
printf("%d", rand);//以十进制方式打印,出来的是地址
return 0;
}

namespace空间—–是一个域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
//定义了一个叫b的命名空间,命名空间是全局的
namespace b
{
//他们还是一个全局变量,放到静态域,
int rand = 0;//加这个就是为了避免冲突
int a = 1;
}

int a = 0;//全局域

int main()//函数里面叫局部域
//编译器会先在局部去找,再去全局去找,否则就会报错
{
printf("%d\n", rand);
printf("%d\n", b::rand);//::叫做域作用限定符,意思是去取左边这个域里面的,很明显要去b这个域里卖弄去找
int a = 1;
printf("%d\n", a);//a为局部域里面去找
printf("%d\n", ::a);//a到左边的空白域里面去找,就是去全局域里面去找
return 0;
}

namespace也可以嵌套包含
对嵌套包含的要用两层::

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//命名空间里面还可嵌套
namespace bt//命名空间里面不仅可以定义变量,还可以定义函数,类型
{
int rand = 10;
int add(int left, int right)
{
return left + right;
}
struct node
{
int val;
struct node* next;
};
namespace n1
{
int d = 2;
}
}

//使用命名空间里面的的东西
//不同文件里面定义的同样的命名空间,会被合并成一个,命名空间
int main()
{
bt::rand = 10;//使用其与其他进行隔离
struct bt:: node nt;//要加bt::才可以找得到里面的东西
bt::add(1, 2);//这样就可以找到了,只是影响了编译器找的规则
bt::n1::d = 2;//运用指定,这样子可以把嵌套的引用出来
//这种可以做到最好的命名隔离,
return 0;
}

c++的输入输出

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
//输入输出流

//using namespace std;
//c++库的实现定义在一个std的一个命名空间中,


int main()
{
cout << "hello world" << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
#include<iostream>
using std::cout;
using std::endl;
using std::cin;
int main()
{
std::cout << "hello world" << std::endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using namespace std;
//cout可以自动识别类型
int main()
{
cout << "hello world" << endl;//endl是用来换行的
double d = 1.2;
int k = 12;
cout << d <<" "<<k<< endl;//自动识别类型不用%d,不可以控制小数点的位数,c++d输入输出可以混在一起写
cin >> k >> d;//把数据流向k和d
return 0;
}

缺省

全缺省和半缺省

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//缺省参数
void func1(int a = 0)//如果func1里面传了参数,就和传的是一样的,如果func1里面没有传参数,相当于实参是0,形参是a
//把缺省值去当作实参传给a
{
cout << a << endl;
}

void func2(int a = 1, int b = 2, int c = 3)//全缺省
{
cout << a << " " << b << " " << c << endl;
}

//半缺省,缺省一部分参数,必须从右往左缺省,必须连续缺省
void func3(int a,int b=32,int c=89)
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
func(1);
func1();
func2();//啥都不传
func2(13, 42);//从左往右给
func3(13);//a必须要传,缺省的可以不传
return 0;
}

struct stack
{
int* a;
int top;
int capacity;
};

void stackinit(struct stack*ps,int capcity=4)
{
ps->a = (int*)malloc(sizeof(int) * capcity);
ps->top = 0;
ps->capacity = capcity;
}

/*int main()
{
struct stack st;
stackinit(&st);//不知道栈最多可以存多少数据,就用缺省值初始化
stackinit(&st, 100);//知道最多可以存多少数据,就传值,减少增容次数,提高效率
return 0;
}*/

//缺省参数不能同时在声名与定义中出现,推荐大家写在声名,方便查找

函数重载

函数重载:是函数的一种特殊情况,同一个函数有不同功能, 这些同名函数的形参列表(参数个数或类型或顺序)不同,用来处理实现功能类似的不同问题

函数名相同的不同函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//1.参数的类型不同
int add(int left, int right)
{
cout<<left << " " << right << endl;
return left + right;
}

double add(double left, double right)
{
cout << left << " " << right << endl;
return left + right;
}

//2参数个数不一样
void f()
{
cout << "f()" << endl;//没传参数就调用他
}

void f(int a)
{
cout << "f(int a)" << endl;//传了参数就调用他
}


//顺序不同

void fs(int a, char b)

{
cout << " fs(int a, char b)" << endl;
}

void fs(char b,int a)

{
cout << " fs(char b,int a)" << endl;
}

//返回值不同不能构成承载,返回值不同不能区分,参数不同可以区别的
//void a(int a)
//int a(int a)

//缺省值不同,不能构成重载

//1.
//void cz(int a)
//{
// cout << "as" << endl;
//}
//void cz(int a = 21)
//{
// cout << "asd" << endl;
//}

//2.形参个数不同,构成重载,但是使用的时候要小心

void as()
{
cout << "ead" << endl;
}
void as(int a = 21)
{
cout << "as" << endl;
}

int main()
{

cout << add(3, 2) << endl;
cout<<add(3.3, 8.3)<<endl;
f();
f(1);
fs(2, 's');
fs('a', 32);
as();//存在歧义
return 0;
}

引用

引用就是给别的变量取别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
int a = 10;
int b = a;//把a的值赋给b
//引用定义。
int& c = a;//引用,在变量名和类型的中间就是一个引用,是a的引用,
//c是a的别名
//c和a的地址和值都是一样的
//引用在语法层,我们要理解为这里没有开一个新空间,就是对原来的取了一个新名称叫做b
//如李逵,黑旋风都是一个人
int a = 20;//a和c都改成20,
int c = 30;//c和a都改成了30,
//如李逵吃饭了,黑旋风也吃了
//黑旋风吃饱了,李逵也吃饱了
int* p = &a;//单独就是取地址
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
int a = 0;
int& b = a;//相同类型不会产生临时变量
double c = 1.1;
int m = c;//会产生一个临时变量,
//同样类型的截断,类型的提升都会产生一个临时变量
//函数的返回
//传值返回也是临时变量


const int& r = c;//r是c的临时变量的别名,把精度丢了
cout << r<<m << endl;
return 0;
}

引用的特性

1.引用必须初始化
2.一个变量可以有多个引用
3.引用只能用一个实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main()
{
//1.引用在定义的时候必须初始化,引用是取别名,要说清楚是给谁取别名
int a = 10;
int& b = a;
//2.一个变量可以有多个引用,
int k = 3;
int& kt = k;
int& ktt = k;//相当于可以有很多个外号,
int& m = kt;//这样也可以,kt,k,m,kkt都是一个空间,一个人可以有很多个名字
//3.引用一旦引用了一个实体,再不能引用其他实体了,
int d = 10;
int& dt = d;
int c = 20;
//1.这里有两层,是让dt变成c的别名呢? 否
//还是把c赋值给dt呢? 是
//经过调试是把c赋值给dt,dt和d的值都变成了20
dt = c;
return 0;
}

引用的应用

1.引用做函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}

//cpp的实现
//1.做参数
void swap(int& r1, int& r2)//r1是x的引用,x的别名,传引用,传地址和传引用功能相同 ,形参的改变可以改变实参
{
int t = r1;
r1 = r2;
r2 = t;
}

//传值和传地址,传引用构成重载,但是调用时,传值和传引用存在歧义,不知道是传值还是传引用,
int main()
{
int x = 0, y = 1;
swap(&x, &y);
swap(x, y);//传引用
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void slistpushback(slistnode*& phead, slistdate x)//指针的引用,别名不混在一起,phead的改变就是改变plist,不用传二级指针,好认识
{
slistnode* newnode = buynode(x);
//那么我们这个时候要找尾
//找到尾节点的指针
if (phead == NULL)
{
phead = newnode;
}
else
{
slistnode* tail = phead;//我们要让tail走到尾部去,而非走到空
while (tail->next != NULL)
{
tail = tail->next;
}//找到了尾节点,链接新节点
tail->next = newnode;
}
}
1
2
3
4
5
6
7
8
9
10
//指针也可以使用引用
int main()
{
int a = 10;
int& b = a;
int* p = &a;
int*& p2 = p;
//p2也是地址,p也是地址,p2是p的别名
return 0;
}
1
2
int* single(int* num, int numsize, int& returnsize)//returnsize就是传回去的长度的别名
{}

2.引用做返回值

传值返回会有拷贝,就会有空间
传引用返回就不会有拷贝,原样返回
如果使用的变量在函数使用完之后没有销毁或者返回的空间较大,就用引用返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

int dd(int a, int b)
{
int c = a + b;
return c;//返回的是c的临时拷贝,相当于int tmp=3被传回去了,只能调用一次,调用完了之后就会变成随机值
}

int& d(int a, int b)
{
int c = a + b;
return c;//f这里返回的就是c别名,
//c传给int& tmp,就是c,所以不会有拷贝造成的空间浪费
}

//但是如果函数返回的时候,返回的对象出了函数的作用域还没有销毁,就可以用传引用返回
int& cout()
{
static int n = 12;
n++;
//……
return n;//n是一个静态变量,出了函数域不会消失,所以用传引用返回
}

1
2
3
4
5
6
7
8
9
10
11
int main()
{
int ret = dd(1,2);
//int& ret=dd(1,2),这样是不行的
int r = d(1, 2);
//这样写会存在非法访问,因为add(1,2)的返回值是c的引用,所以add栈帧销毁了以后,回去访问c位置的空间
//如果add函数栈帧销毁了,清理空间那么取c值的时候,取到的就是随机值,给ret就是随机值,当然这个取决于编译器的的实现
cout << ret << endl;
cout << r << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct stack
{
int* a;
int top;
int capacity;
};

void func1(struct stack st)//传值参数,会有拷贝,有空间开辟,原样拷贝
{

}

void func(struct stack* st)//传地址参数,指针开辟了4个字节
{

}

//引用做参数,随时都可以,可以1.提高效率,2.形参的修改可以影响实参,输出型参数
void func(struct stack& rst)//传引用参数,rst就是st的别名,没有开辟空间
{

}

int main()
{
struct stack st;
func1(st);
//指针和引用可以构成函数重载,因为指针和引用是两种不同类型的参数
func(&st);
func(st);
return 0;
}

//传引用返回的价值
//1.提高效率,2.修改返回变量

int& at(int i)//处理作用域,还在就很有用
{
static int a[10];//第一次进入就创建了一个数组
return a[i];//返回这数组第i个位置的别名
//变成了可读可写的,
}

常引用

用const进行修饰,在做参数的时候尽量都加一个const进行修饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//尽量使用const引用进行传参
void f(const int& a)
{
cout << a << endl;
}

//const 引用通吃
// const type& 可以接收各种类型的对象

int main()
{
//权限被放大了,a从一个只读不写的,变成b一个可读可写的就是错的
//const int a = 10;
//int& b = a;

//权限不变,就可以
const int a = 10;
const int& b = a;//

//权限缩小
int c = 2;
const int& d = c;//从可读可写的变成只读不写的是可以的

double d = 1.11;
int i = d;//类型转化,把d的整形部分转过去,不加引用可以
//int& k = d;//
//d的临时变量是具有常性,只读不写,而int&具有可读可写,
//普通引用引的是左值,const引用引的是右值
//临时变量具有常性,不能被修改,
//const 接收就是常量了
return 0;
}

引用的优点:

引用和指针的不同点
1.引用在概念上定义一个变量的别名(没有开空间),指针存储一个变量的地址
2.引用在定义的时候必须初始化,指针最好初始化,但是不初始化也不会报错(会变成野指针)
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个相同类型的实体(如我们在链表的时候指向不同的地方)
4.没有NULL的引用,但是有NULL 的指针
5.在sizeof里面的含义不同,引用为引用类型的大小,但是指针是地址空间所占字节大小(32位下是4个字节或64位里面的8个字节
6.引用自加**++是引用的那个实体+1**,而指针是指针指向偏移一个类型的大小
7.访问实体方式不同,指针需要解引用,引用编译器里面会自己处理
8.有多级指针没有多级引用,引用比指针更加安全

extern “C”

c++程序,调用c的库,再c++程序中加extern “C”
告诉c++编译器,{}里面的函数是用c的编译器编译的,链接的时候,用c的函数名规则去找,就可以链接上了,只有c++才认识extern “c”

c程序,调用c++的库,再c++库中加extern “C”
c掉c++,c++的静态库就会用c的函数名修饰规则去处理以下函数,所以就不能用重载

1
2
3
4
5
6
//在c程序调用c++时候,在c++库里面加extern "C"
#ifdef __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif // __cplusplus

内联函数inline

我们在每次调用函数都会建立栈帧,有栈帧就会有消耗,需要建立栈帧,栈帧中要保存一些寄存器,结束后又要恢复,
可以看到这些都是有消耗的,对于频繁调用的小函数,能否优化一下呢,
c语言可以用宏,来操作避免栈帧的建立

1
#define ad(x,y) ((x)+(y));

c++的实现方法inline,内联函数

有了inline就不需要去用c的宏,因为宏很复杂,很容易出错 inline但是展开的话,指令就会特别多
1.inline是一种以空间换时间的做法,省去调用函数栈帧的过程,所以代码量长的函数,或者递归函数就不适合用inline,展开之后就会有特别多的指令,变得很大
所以小函数比较适合用inline
2.inline对于编译器只是一个建议,编译器会自动识别,递归或长的编译器会自动忽视inline
3.inline函数声名和定义不能分离,.h和.cpp都要加一个inline,

结论:短小频繁使用的函数就定义成inline

1
2
3
4
5
6
7
8
9
10
11
12
inline int add(int x, int y)//这样就不会建立栈帧,内联函数,默认是不会展开的,没有函数调用了
{
int ret = x + y;
return ret;
}

inline void swap(int& a, int& b)//swap也会经常会出现
{
int tmp = a;
a = b;
b = tmp;
}

c++入门
http://example.com/2021/12/29/c++入门/
作者
Zevin
发布于
2021年12月29日
许可协议