c++入门
C++入门知识主要是解决C语言的一些不足之处,我们下面直接步入正题。
我们下面来学习c++的第一个程序:
举例:`
#include <iostream>
//std是所有c++库命名空间
using namespace std;//放到全局
int main()
{
cout << "hello world" << endl;
return 0;
}
我们学习这个程序之前要先知道一个知识点:命名空间域。
命名空间
我们在使用C语言的时候,通常会遇到变量定义冲突的情况,而C++中的命名空间域就是来解决这个问题的,下面我们来学习这个知识点:
域的概念
首先,域有以下几个分类:
其中全局域和局部域都会影响生命周期和访问,而命名空间域只影响访问。
而类域这里追秋还没有学到,所以这里就不给大家激讲解了。
下面我们写一个域的程序要让我们更快的了解域的概念:
namespace hhd1
{
int x = 0;
}
namespace hhd2
{
int x = 1;
}
int main()
{
printf("%d\n", hhd1::x);
printf("%d\n", hhd2::x);
return 0;
}
其中namespace就是域的意思,hhd1和hhd2就是这个域的名字,我们在使用指定域中的变量的时候是用::符号来使用,::就是域作用限定符,作用是指定编译器去访问哪一个域。
接下来我们看下一个程序:
namespace hhd1
{
int x = 111;
}
using namespace hhd1;
int main()
{
printf("%d\n", x);
return 0;
}
讲解:using namespace hhd1 就是将命名空间域hhd1展开,使编译器可以访问到该空间中,
我们在命名空间域中还可以定义一些常见的量,比如:函数,结构体。
namespace hhd1
{
int x = 0;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
printf("%d\n",hhd1::x);
//定义结构体指针
struct hhd1::Node phead;
return 0;
}
其中我们看到函数、结构体也是可以在命名空间域中定义的,其中在主函数中还定义了结构体指针。
命名空间域的嵌套
举例:
#include <iostream>
namespace hhd
{
namespace sz
{
int x = 111;
}
namespace ls
{
int x = 120;
}
}
int main()
{
printf("%d\n", hhd::sz::x);
printf("%d\n", hhd::ls::x);
return 0;
}
我们在使用命名空间域的时候可以对域进行嵌套定义,这样就可以防止在同一域中的变量定义冲突的问题,当然,这个嵌套是可以多次嵌套的,只要你能把这个逻辑理清楚的话。
域的使用方式
1.不展开,在指定域搜索
#include <iostream>
namespace hhd
{
int x = 120;
}
int main()
{
printf("%d\n", hhd::x);
return 0;
}
2.使用using将命名空间中某个成员引入
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
3.使用using namespace 命名空间名称 引入
using namespce N;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
Add(10, 20);
return 0;
}
C++输入输出
输出cout
使用方法:
int main()
{
cout << "xxxxx" << endl;
return 0;
}
int main()
{
int x = 0;
double y = 1.2;
cout << x<< y << endl;
return 0;
}
在对cout语句使用的时候不需要像printf中一样注明类型,就可以直接打印在显示器上。
输入cin
举例:
int main()
{
int x = 0;
double y = 1.2;
cin >> x >> y ;
cout << x << y << endl;
return 0;
}
缺省参数
举例:
#include <iostream>
using namespace std;
void func(int i = 0)
{
cout << i << endl;
}
int main()
{
func(1);
func();
return 0;
}
运行结果:
缺省参数有以下几个分类:
全缺省
void func(int x = 10, int y = 20, int z = 30)
{
cout << "x="<< x << endl;
cout << "y="<< y << endl;
cout << "z="<< z << endl<<endl;
}
int main()
{
func(1, 2, 3);
func(1, 2);
func(1);
func();
return 0;
}
运行结果:
半缺省(部分缺省)
void func(int x, int y = 20, int z = 30)
{
cout << "x="<< x << endl;
cout << "y="<< y << endl;
cout << "z="<< z << endl<<endl;
}
int main()
{
func(1, 2, 3);
func(1, 2);
func(1);
return 0;
}
运行结果:
函数重载
函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
函数重载有以下几个分类:
参数类型不同
// 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(double left, double right)" << endl;
return left + right;
}
函数名相同,但是参数的类型不同。
参数个数不同
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
函数名相同,但是函数的参数数量不同。
参数顺序不同
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;
}
函数名相同,函数的参数顺序不同,这一个不同的点影响着编译器中对于函数搜索规则的变化,这个是C语言所没有的。这个点之后的文章追秋会重点讲解!
引用
一个变量可以有多个别名
举例:
int main()
{
int a = 0;
//引用,b就是a的别名
int& b = a;
cout << &b << endl;
cout << &a << endl;
//可以取多次别名
int& c = a;
//也可以对别名取别名
int& d = b;
cout << &c << endl;
cout << &d << endl;
return 0;
}
运行结果:
下面来举一个例子来引出引用在函数方面的使用:
void Swap(int& c, int& d)
{
int tmp = c;
c = d;
d = tmp;
}
int main()
{
int a = 1;
int b = 5;
Swap(a, b);
cout << a << endl << b << endl;
return 0;
}
运行结果:
此时ab中的值已经被交换,那么这个时候就有人要问了,在函数形参取名的时候可以和实参一样吗?
解答:可以!
引用在定义时必须初始化
int main()
{
int a = 0;
int& b = a;
int& c;
return 0;
}
我们可以看到上述代码在编译的时候报错。
为什么引用必须初始化?
引用如果不初始化的话,那么回合赋值产生歧义。
int main()
{
int a = 0;
int b = 10;
int d;
int& c;
c = b;
d = b;
return 0;
}
上述代码c的引用和d的赋值部分根本分不清楚。
引用一旦引用一个实体,再不能引用其他实体
引用一旦确定引用一个实体之后,就不能在引用另一个实体了。
int main()
{
int a = 0;
int b = 10;
int& c = a;
&c = b;
return 0;
}
下面说几个引用在函数传参的应用:
void PushBack(struct Node*& phead, int x)
{
phead = newnode;
}
int main()
{
struct Node* plist = NULL;
return 0;
}
此处就是函数中的phead就是struct Node* plist的别名,也就是phead等价于plist;
使用场景
参数
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
返回值
场景一:
int func()
{
int a = 1;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
场景二:
int& func()
{
int a = 1;
return a;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
场景三:
int& func()
{
int a = 1;
return a;
}
int main()
{
int& ret = func();
cout << ret << endl;
return 0;
}
此时在这个场景下做进一步的改动:
int& func()
{
int a = 1;
return a;
}
int& fun()
{
int b = 6;
return b;
}
int main()
{
int& ret = func();
cout << ret << endl;
fun();
cout << ret << endl;
return 0;
}
运行结果:
在以上这种场景中,只是简单调用了另一个函数,其实啥都没做,这里就会出现随机值。
指针和引用的区别
内联函数
#define ADD(a,b) ((a) + (b))
int main()
{
int a = 1;
int b = 2;
int c = ADD(a, b);
cout << c << endl;
return 0;
}
以上代码就是宏的使用的一个简单代码,其中也会出现几个问题:为什么a、b要单独加括号将他们单独放在一起:
解答:因为可能原始参数可能传的不仅仅是一个参数,而是一个表达式,下面我们来看:
#define ADD(a,b) ((a) + (b))
int main()
{
int a = 1;
int b = 2;
int c = ADD(a + b , a - b);
cout << c << endl;
return 0;
}
上面这个例子就很好的说明了宏的一个正确使用方式。相比较于函数,宏是不需要建立栈帧的,也就减少了栈帧空间的开销,当然宏也不是完美的,宏也有缺点:
而内联函数就将函数和宏的优势结合在了一起:
我们知道:函数在传递参数的时候,如果参数是表达式,那么会将表达式的值算好在传值,而宏是不需要单独建立栈帧,编译器在编译的时候会自动将宏的内容进行替换,这里内联函数就将这一点完美的应用到了:
inline int ADD(int x, int y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
int c = ADD(a + b , a - b);
cout << c << endl;
return 0;
}
这里的运算不建立栈帧,直接在原地进行替换且参数是运算好的。
内联函数特性
- inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
- inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
- inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。下面将一个例子来说明这个问题:
下面将一个内联函数一个在实践当中的一个小应用的地方,
当一个函数的声明和定义没有分离的时候,函数在运行的时候会出现报错:
想要解决这个问题我们有以下几个解决方法:
auto关键字
识别类型
int main()
{
int aw = Add(1, 2);
int a = 1;
auto b = a;
auto c = &a;
auto* d = &a;
return 0;
}
在上述的情况下,定义一个新的数据就不用特意声明他的类型,系统会自动识别数据类型并完成定义。
auto关键字也可以定义函数类型:
当然在这里自动识别类型定义显得价值不大,但是在以后类型很复杂的情况下,auto函数的价值就会显示出来。
auto不能推导的场景
不能作函数的参数
编译器允许auto作为返回类型但不能作为参数。
不能用来声明数组类型
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
基于范围的for循环(C++11)
范围for的语法
int main()
{
int a[] = { 1,2,3,4,5 };
for (auto e : a)
{
cout << e << endl;
}
return 0;
}
这其实就是一种新的for循环的使用方式,auto会将数组a中的值依次放到e中,然后我们打印的结果就是:
范围for的使用条件
指针空值nullptr(C++11)
这个点其实是在弥补C++之前的缺陷:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0。