引言
说到编程语言常常听到的就是C语言C++ Java 。C语言是面向过程的,C++是和Java是面向对象的,那么什么是面向对象呢?什么又是面向过程呢?C++是什么?封装、继承、多态是什么?且听我絮絮叨叨。
C++入门基础
1.命名空间
1.1 概念和作用
在C语言中同一个作用域不能出现同名变量和函数,但在C++中可以因为C++提出了命名空间的概念,命名空间是一个单独的作用域。命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。用语言描述描述这些东西太过于晦涩直接上代码!
命名空间的格式很简单只需要一个关键字namespace + 空间的名字 + {} 。那么如何访问命名空间中的内容呢?在获取命名空间中的变量时只需空间的名字加上"::"就可以访问命名空间的中的内容。
namespace zh
{
//这一是叫zh的命名空间
int i = 2;
void fun()
{
printf("这是zh\n");
}
}
namespace hh
{
//这是叫hh的命名空间
void fun()
{
printf("这是hh\n");
}
int i = 1;
}
int mian()
{
zh::fun();
hh::fun();
printf("%d\n", zh::i);
printf("%d\n", hh::i);
return 0;
}
1.2 using关键字
通过using关键字可以引入命名空间中的变量,或是直接将命名空间引入。
namespace zl
{
int c = 0;
}
using namespace hh;
using zl::c;
int main()
{
printf("%d\n", c);
fun();
return 0;
}
2.C++的输入和输出
在C语言中的输入和输出是借助printf和scanf进行操作的,但是在C++中不使用这两函数进行输入和输出而是使用cout和cin这两个对象需要包含头文件<iostream>,在C++在包含头文件时不需要在头文件后面加.h。这里也需要大家了解到是这使用cout和cin时我们需要引用std这个命名空间这是C++标准库,C++将标准库的定义和实现都放在这个名空间内。在日常的代码练习中建议直接展开这个命名空间,这样可以方便书写不展开的话如果想使用cout就需要在cout前面加上命名空间即std::cout。(这里只是简单的使用方法不涉及底层)
cout是标准输出流对象(控制台),cin是标准输入流对象(键盘),cout和cin的优势在于自动识别数据的类型。下面这两组代码的作用都是一样的你更愿意写哪种?
int main()
{
int a = 9;
//1
printf("%d", a);
scanf("%d", &a);
//2
cout << a;
cin >> a;
return 0;
}
3.缺省参数
3.1 概念
缺省参数我愿称其为备胎,缺省参数的作用是在定义或者声明时可以给函数的参数一个缺省值,当调用函数时不进行传参这时在编译器就会将缺省值当做默认的参数进行传参。注意在声明和定义式不能同时给缺省值。
//void func(int i = 10);
void func(int i = 10)
{
cout << i << endl;
}
int main()
{
func();
func(1);
return 0;
}
如果放开注释就会报错
3.2 全缺省
全缺省的意思就是函数声明或者定义时所有的参数都给缺省值
void func1(int a = 1, int b = 2, int c = 3)
{
cout << a << " " << b << " " << c << endl;
}
int main()
{
func1();
func1(8, 9, 10);
return 0;
}
3.3 半缺省
半缺省顾名思义就是给一半的缺省参数但是需要注意的是一半的缺省参数需要从右往左给。因为只有一部分的参数有缺省参数,另一部分没有缺省参数所以在传参的时候是从左往右传参的。
void func1(int a = 1, int b = 2, int c = 3)
{
cout << a << " " << b << " " << c << endl;
}
void func2(int a, int b, int c = 100, int d = 900)
{
cout << a << " " << b << " " << c <<" "<< d << endl;
}
int main()
{
func2(1, 2);
func2(100, 200);
return 0;
}
4.函数重载
如何构成函数的重载,当函数名相同形参的类型以及个数或者类型顺序不同时就可以构成函数的重载。直接上代码
//参数类型不同
int add(int a, int b)
{
cout << "add(int a, int b)" << endl;
return a + b;
}
double add(double a, double b)
{
cout << "add(double a, double b)" << endl;
return a + b;
}
//参数个数不同
int add(int a)
{
cout << "add(int a)" << endl;
return a;
}
//参数的顺序不同
void print(int a, char b)
{
cout <<"print(int a, char b)"<< endl;
}
void print(char b, int a)
{
cout <<"char b, int a"<< endl;
}
int main()
{
add(1, 2);
add(1.0, 2.0);
add(100);
print(1, 'c');
print('c', 1);
}
为什么C++支持函数重载呢?
因为在C++中在进行编译的时候C++会对函数名进行修饰通过参数的不同或者返回值的不同对函数名进行修饰给函数名添加特殊符号等,使得链接时每个函数名都对应着不同的函数。
但是C语言没有函数名修饰的机制,函数进行编译后就直接添加到符号表中,所以无法区分同名的不同函数。
5.引用
5.1 概念
引用是对一个已经初始化过的变量重新起一个别名,这个别名指向那个变量所在的位置,编译器不会为引用开辟空间,引用和被引用的变量共用一块空间。
int main()
{
int a = 0;
int& b = a;
cout << a << endl;
cout << b << endl;
return 0;
}
5.2 引用的特殊性质
1.引用定义时必须初始化。2.一个变量可以有多个引用。3.引用一个元素后就不能引用其他元素。
int main()
{
int a = 0;
int& b = a;
int& i = a;
int c = 9;
b = c;//这段代码并不会改变b的引用而是将c的值付给b,又因为b是a的应用所以改变的是a的值
cout <<"b = "<< b << " "<<"i = " << i << " " << "a = " << a << endl;
return 0;
}
5.3 常引用
顾名思义常引用就是对常量进行引用,但是C++中规定不能对一个常量直接进行引用,常量即为不能被修改的量。这里就需要就需要引入新的知识点,权限的放大和缩小。
权限的放大:对于一个被const修饰的对象,是不能被绑定到一个非const的引用上,当一个非const的引用绑定到const修饰的对象上时,因为被const修饰的对象是不能被修改,但是非const修饰的引用是可以随意修改的,这就导致了权限的放大(不能修改->可以随意修改),这就导致了两个概念相悖。
权限的缩小:对于一个常量对象,是可以被绑定到一个const修饰的引用上的,因为常量对象不能被修改,const修饰的引用也不能被修改,原本的引用是可以随意的修改但是加上了const修饰就将其的权限进行了缩小,在C++中权限可以被缩小但是不能被放大。
int main()
{
//将一个const修饰的对象进行权限的缩小
const int a = 9;
const int& c = a;
//将一个变量的引用用const修饰,引用本身是可以随意修改的但是加上了const就不能修改了也是权限的缩小
int b = 7;
const int& z = b;
b = 2;//但是b变量本身是可以修改的因为b没有被const修饰
return 0;
}
5.4引用的使用
C语言和C++是极度追求效率的语言,引用就能极大的提升效率。为什么说引用可以提升效率呢?下面举两个例子:1.引用做函数的参数 2.引用做函数的返回值
1.引用做为函数的参数
int Add1(int& a1, int& b1)
{
return a + b;
}
int Add2(int a2, int b2)
{
return a + b;
}
int main()
{
int a = 9;
int b = 8;
Add1(a, b);
Add2(a, b);
return 0;
}
上面这两段代码功能都是一样,但效率上不同。引用作为函数的参数也是就说a1是a的一个别名也就说a1是没有开辟空间的,但Add2中a2在调用的时候是需要开辟一个临时新的空间用于存储形参这个过程是需要时间的。因为计算机的计算速度非常的快,所以这两个函数调用时效率上没有区别但是如果当在调用的时候进行大量的传参呢?比如对一亿个数据进行排序呢?这样差距就体现出来了。所以在C++中经常用到引用做参数。
2.引用做函数的返回值
int& Add(int a, int b)
{
int c = a + b;
return c;
}
const int& Add1(int a,int b)
{
return a + b;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
注:函数的返回值不是直接进行返回而是先创建一个临时变量,将临时变量的值赋给接收函数返回值的变量。临时变量是具有常性的Add1函数中返回值是cosnt类型引用的返回值,为什么要加const是因为Add1返回的是一个临时的变量具有常性所用要用const进行修饰缩小权限。
所以上述代码的结果是什么?
上述代码的结果是随机值。因为:Add函数是引用做返回值,所以返回的是一个临时的变量这个临时变量,用了一个ret引用接收ret和这个临时变量共用一个空间。这函数的栈帧结束之后这个临时变量就销毁了,临时变量销毁了所以ret就没有指向的内容了,即便在调用了一次Add因为没有用ret接收所以,这段代码运行的结果是随机值。
5.5 引用和指针的区别
指针和引用的区别是一道经典的C++面试题。
1.从内存的角度来说指针在创建的时候是需要创建空间存储地址的,但引用不需要引用就是给原本的空间起一个别名。
2.指针可以不进行初始化,但是引用必须进行初始化。
3.有多级指针但是没有多级引用。
4.指针可以随意改变指向,但是引用不可以。
5.在sizeof中含义不同,引用返回的是引用类型的大小,而指针在32位下(4个字节)和64位下(8个字节)返回值不同。
6.引用加1即被引用的对象进行加1,指针加一是指针向后偏移一个类型的长度。
7.访问实体的方式不一样,引用编译器自动处理,指针需要解引用。
8.指针会出现野指针和为空的情况,而引用不会所以引用更加安全。
6.内联函数
6.1概念
被inline修饰的函数被称为内联函数,内联函数也是用来提高效率的。内联函数会被调用的地方进行展开,展开的目的就是不创建函数的栈帧提高运行效率。
6.2特性
1.内联是一种以空间换时间的做法,在调用的地方直接将函数展开会使目标文件会变大。
2.内联只是对编译器的一种建议,编译器可以选择不采用。假设你将一个要递归多次的函数定义为内联函数那么编译器是不会对其进行展开的。
3.内联函数不能进行函数的定义和分离,因为内联函数在编译阶段是直接被展开的,如果定义和申明分离就会导致找不到函数的地址就会导致无法连接错误。
为了实现内联展开,编译器需要在编译时看到函数的完整定义。如果内联函数的定义和声明分离,编译器在看到声明时无法进行内联展开,因为它没有函数体的具体实现。
如果内联函数的定义和声明分离,并且定义仅在一个翻译单元中可见,那么其他翻译单元在链接时将无法找到该函数的实现,导致链接错误。
7.auto关键字
7.1概念
auto关键字是自动推导类型。当定义类型比较难拼写时可以实用auto(vector的迭代器)。
int main()
{
auto a = 8;//这时auto就是整形
auto b = 8.9;//这是auto就是double类型
//这里打印一下a b 的类型
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
return 0;
}
7.2 特性
1.auto定义的时候必须初始化,因为如果不进行初始化编译器就无法推导auto定义的变量类型。
2. auto和auto*没有什么区别,但是auto引用必须加&
因为auto是可以自动推导类型的如果初始化变量的是一个地址那么auto就会初始化为个一指针类型,但引用不能省略&因为auto无法推导。
int main()
{
int a = 0;
auto b = &a;
auto* c = &a;
auto& v = a;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(v).name() << endl;
return 0;
}
3.使用auto时不能进行一行有多个变量进行定义
4.auto不能用来定义函数的参数
void dunc(auto a)//因为这使用auto在编译时无法推导出变量的类型
{
cout << "dunc" << endl;
}
5.auto不能用来定义数组(C++的规定)
8.范围for的使用和语法
如果想要遍历一个数组应该怎么写?
int main()
{
//常规写法
int a[] = { 1,2,4,5,6,7,8 };
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
cout << a[i] << " ";
}
cout << endl;
//范围for写法
for (auto e : a)
{
cout << e << " ";
}
return 0;
}
9.空指针nullptr
NULL是如何被定义的在C++98中NULL是一个宏被定义为(void*)指针或者0,所以这使用NULL指针时我们就会遇到麻烦。所以C++就引入了nullptr用来解决NULL的问题,相比于NULL来说nullptr更加安全。
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;
}
总结
综上所述就是C++的一些入门知识点的讲解,这些知识点比较细碎不好理解但是这些知识点都是非常有用的在,某些特殊的场景下你会惊叹这样的语法设置非常巧妙。打个比方auto虽然看着确实方便但是好像也就一点点方便,举个栗子如果在STL容器中用来定义一些vector List这些容器的迭代器时你就感觉,auto太妙了。总之这些知识点之所以细碎是因为没有放到适合的场景下去体会,后面的博客我会带大家去好好体会其的妙用。
希望大佬们多多指点!也希望大家多多点赞、收藏、关注!