目录
C++是什么
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机,20世纪80年代,计算机界提出了OOP(object oriented programming:面向对象) 思想,支持面向对象的程序设计语言应运而生。
1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此,C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
C++关键字(c++98)
c++98总共有63个关键字。
看似很多,但是如果大家都是从c语言开始学习的就会发现上面有红圈的其实都是c语言有的。
命名空间(namespace)
大白话来说:我们在用c语言是我们定义类似意义的变量时我们通过在后面加数字来避免名字重复。当然不只是变量还有函数名等等只要关于起名字的东西,为了避免重复要考虑怎么起名字。
int a1 = 1;
int a2 = 1;
而对于c++来说,namespace可以用来解决这个情况。
命名空间的定义
1.正常定义
//命名空间的普通定义
namespace NS1 //NS1为命名空间的名称
{
//在命名空间中,既可以定义变量,也可以定义函数
int a;
int Add(int x, int y)
{
return x + y;
}
}
2.嵌套定义
//命名空间的嵌套定义
namespace NS1 //定义一个名为N1的命名空间
{
int a;
int b;
namespace NS2 //嵌套定义另一个名为N2的命名空间
{
int c;
int d;
}
}
3.同一名称的命名空间
对于一个工程而言可以有许多相同名字的命名空间。但是最终会被整合为1个命名空间。所以同名的空间也不能出现相同名字的变量和函数等等。
原理:对于c语言来说,我们所有的函数定义和变量定义都是在一整个大的作用域中。而命名空间就是相当于把这整个作用域划分成了几个新的作用域。就像地球只是一个,但是里面有了不同的国家,那就有了国界。
命名空间使用
那好了我有命名空间了,那我怎么用呢?
有两种方法
1.使用::,这个符号 命名空间名称::命名空间成员,就能拿到作用域中的变量,函数同理。
//加命名空间名称及作用域限定符
#include <stdio.h>
namespace N
{
int a;
double b;
}
int main()
{
N::a = 10;//将命名空间中的成员a赋值为10
printf("%d\n", N::a);//打印命名空间中的成员a
return 0;
}
2.使用using关键字,但这里又有两个分支
一. 只using 单独一个成员
只using一个成员,代表在后面的使用中只有这个成员可以不用::来取出
//使用using将命名空间中的成员引入
#include <stdio.h>
namespace N
{
int a;
double b;
}
using N::a;//将命名空间中的成员a引入
int main()
{
a = 10;//将命名空间中的成员a赋值为10
printf("%d\n", a);//打印命名空间中的成员a
return 0;
}
二.using 整个命名空间
using整个命名空间,代表后续使用这个空间的变量都不需要::,直接使用
//使用using namespace 命名空间名称引入
#include <stdio.h>
namespace N
{
int a;
double b;
}
using namespace N;//将命名空间N的所有成员引入
int main()
{
a = 10;//将命名空间中的成员a赋值为10
printf("%d\n", a);//打印命名空间中的成员a
return 0;
}
声明和定义
声明表示我有这个函数,但我没有具体内容。
定义就是将声明的函数实现。
//test.h
#include <iostream>
#include <stdio.h>
namespace N
{
void Print(int a, int b, int c);
}
//test.c
#include"test.h"
void N::Print(int a, int b, int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
就像c语言里一样我们通常只是在h头文件中把函数声明,然后再c文件里面对齐定义。
但由于现在有了命名空间的加入,我们在实现定义时要将命名空间带上 N::Print..
C++的输入输出
C++在c语言的基础上有新的输入输出方案
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
cout << "hello world!" << endl;
printf("%s\n","hello world!");
return 0;
}
上述的cout endl都是c++中新增的。cout是Console out即控制台输出的意思。和printf一样都是讲内容在控制台上打印。但和printf不同的地方在于:cout没有占位符,printf在输出前你得自己区分你要输出的变量是什么类型。cout则是自动判断。endl 就是 换行的意思。
#include <iostream>
using namespace std;
int main()
{
int i;
double d;
char arr[20];
cin >> i;//读取输入的一个整型
cin >> d;//读取输入的一个浮点型
cin >> arr;//读取输入的一个字符串
cout << i << endl;//打印整型i
cout << d << endl;//打印浮点型d
cout << arr << endl;//打印字符串arr
return 0;
}
在C语言中有标准输入输出函数scanf和printf,而在C++中有cin标准输入和cout标准输出。在C语言中使用scanf和printf函数,需要包含头文件stdio.h。在C++中使用cin和cout,需要包含头文件iostream以及std标准命名空间(建议不展开,用::来使用)。
缺省函数
缺省函数是什么?
缺省函数其实就是给函数的参数一个默认值,如果传参时没有传这个数,那这个参数的值就是默认值
全缺省
顾名思义,全缺省那就是全部参数都有默认值。
void Print(int a = 10, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
半缺省
只要不是全缺省那都是半缺省不管有几个默认值
void Print(int a, int b, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
注意一:半缺省只能从右向左给,并且不能中断
如下如果a c都有默认b没有,那就中断了。如果只有b有c没有,那就是没从右向左,也错了。
void Print(int a = 10, int b, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
缺省函数不能同时在声明和定义中出现
缺省参数值只能在函数声明中指定,而不能在函数定义中指定。(声明是在h文件,定义在c文件)
为什么呢?缺省值可能会因为编译顺序或者包含文件的重复而导致冲突或不一致。为了避免这种情况,C++标准规定缺省参数值只能在函数声明中指定。
//错误示例
//test.h
void Print(int a, int b, int c = 30);
//test.c
void Print(int a, int b, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
缺省的参数只能是全局的或者常数
这个很好理解,因为局部变量,它是有生命周期的,这个函数执行结束后,这个变量就没了。
所以参数只能是全局的或者是常数。
函数重载
大白话说:就是可以有重复名字的函数。
#include <iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
cout << Add(1, 2) << endl;//打印1+2的结果
cout << Add(1.1, 2.2) << endl;//打印1.1+2.2的结果
return 0;
}
如上图我们定义了两个函数,但是其名字是相同的。如果在c语言中出现同名函数就会报错。
注意:虽然函数名一样,但是也有其对应的限制,如果只是返回类型不同,是不可以的。必须是参数的不同,可以是参数的顺序,参数的个数,参数类型。
函数重载原理
我们知道,一个C/C++程序要运行起来都需要经历以下几个阶段:预处理、编译、汇编、链接。
这里在编译阶段时,会把程序里的表达式,函数名,变量等符号,用一张符号表(里面存的对应符号的标识和地址)进行汇总,且分配一个地址,最后在链接时会把不同源文件的符号表进行汇总。若不同源文件的符号表中出现了相同的符号,则取合法的地址为合并后的地址(重定位),前提是没有命名冲突。
回到函数重载,如图在c语言时我们函数在符号表中是直接叫f的。而在c++中,符号表里的标识会根据参数个数参数类型参数顺序做重命名。
不同的编译器采用的方法不同,上图只是模拟。所以根据参数的情况,这时同名的函数是可以存在的。
总结:c++中可以有同名函数,只要参数有不同即可。c语言中不可以,因为c语言直接命名
extern “C”
在函数前加“extern C”,意思是告诉编译器,将该函数按照C语言规则来编译。
对于c++而言大部分情况写c语言的语法是能支持了,但是在编译时由于c++的缘故复杂性较高。且由于许多底层库和系统API都是用C语言编写的,extern "C"
的使用增强了C++程序的跨平台兼容性,使得C++程序能够更容易地在不同操作系统和硬件平台上运行。
这时你的函数就不能重载了,也算是防止命名冲突的手段了,毕竟一个工程的代码多了,可能出现同名函数,不利于使用,如果用extern C就可以避免。
引用
大白话说:引用就是给变量起了个小名,就像英雄联盟里的墨菲特,起的小名叫石头人。
引用的符号:& (不是取地址,而是在类型后面加上,int&)
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;//给变量a去了一个别名,叫b
b = 20;//改变b也就是改变了a
return 0;
}
上边代码就是定义了一个变量a ,现在给他来了个小名叫b,所以他们就是一个东西。
小a原来10岁,小b长到了20岁,那当然a 也是20岁了。
注意:类型要一样,不能int 别名用的char。
引用的特性
一.引用必须初始化。
避免悬空引用:如果引用没有被初始化,它将会包含一个不确定的地址
int a = 10;
int& b = a;//引用在定义时必须初始化
//错误的
int& b;
b = a;
因为引用是起小名,你都没有起小名的对象,那你直接留一个小名备用吗?这不就是闲的。
二.一个变量可以被多次引用
int a = 10;
int& b = a;
int& c = a;
int& d = a;
你人只有1个,但是你的外号小名可能不止一个。
三.引用不能中途变卦
int a = 10;
int& b = a;
int c = 20;
b = c;//你的想法:让b转而引用c
一开始这个小名就是给你起的,不能把这个小名又换给别人,这样可能就会导致指定不清。
就像你有10个兄弟,每个人小名都一样 阿龙,妈妈找阿龙时,可能就指向不清了。
上面你想把b这个小名再给c,但实际上是把a的值改成c的值。
常引用
一个常数只能用来赋值,但是并不能被修改。但是常数可以有别名吗?当然可以
int main()
{
const int a = 10;
//int& ra = a; //该语句编译时会出错,a为常量
const int& ra = a;//正确
//int& b = 10; //该语句编译时会出错,10为常量
const int& b = 10;//正确
return 0;
}
const 在 int 前,表示 a 这个变量的值不能被修改,所以引用时也要有限制,前面要加上const。(防止引用的小名被更改,导致a被更改)。
同理常数就是不可更改的,所以用const修饰后就可以给常数起别名。
对于指针来说const可以在类型前或者类型后,表示两种含义。但是引用只能在前面。
引用的场景
一.引用参数
//交换函数
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
既然说引用就是小名,所以引用参数的参数就是传入进来的变量。 所以里面改变外面也会改变
三种参数:1.传值:临时开辟一个变量,用来存储调用函数时传入的变量的值。(复制体)
2.传指针:传入进来的是一个指针,所以就是一块地址(需要解引用),这块地址存的就是传入的变量。(本身)
3.传引用:传入的是调用函数的变量的本身,相当于参数就是外面变量的别名。(本身)
二.返回值引用
int& Add(int a, int b)
{
static int c = a + b;//static使c变为静态
return c;
}
做返回值,因为引用就是起小名,所以return什么传出去的就是什么。而不是只是传值。
但话又说回来,既然要把结果原封不动的传出去,那它就不能return局部变量,因为局部变量的生命周期就在这个函数以内,只要函数结束局部变量的含义就没了,此时外面如果用这块地址的话。就类似于野指针了,有风险。
引用和指针的区别
语法上说,引用就是别名,所以就是变量本身。但是,在实际上,引用也是有空间开辟的,和指针类似。
int main()
{
int a = 10;
//在语法上,这里给a这块空间取了一个别名,没有新开空间
int& ra = a;
ra = 20;
//在语法上,这里定义了一个pa指针,开辟了4个字节(32位平台)的空间,用于存储a的地址
int* pa = &a;
*pa = 20;
return 0;
}
如图发现二者的汇编码都是类似的,证明引用的底层原理是类似于指针的,也是存的变量的地址。
引用和指针的区别!!!!(重点):
1、引用在定义时必须初始化,指针没有要求。
2、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
3、没有NULL引用,但有NULL指针。
4、在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
5、如果对引用 进行 a++等操作,是数据的改变。对指针进行 *a++等操作,则是对地址的操做,改变的是一个指针大小的地址。
6、有多级指针,但是没有多级引用。
7、访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理。
8、引用比指针使用起来相对更安全。(野指针,但没有野引用。因为引用一开始就初始化好了,后续不能改变)
内联函数
什么是内联函数
白话说:就是把函数在使用的位置重新写一遍(计算机来做)。
好处:通常我们调用函数是先找到函数的地址,然后再函数启动。中间多了一步调用。内联函数把函数在使用位置展开,减少了找地址的消耗。
关键字:inline,在函数头位置前加上
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int ret = Add(1, 2);
return 0;
}
如图可以看出,普通函数在汇编层面多了一个call指令(找地址)。而内联函数则直接执行了函数。
inline内联函数的特性
inline可以把函数在调用位置展开。
1.inline以空间换时间。如果一个内联函数被多个地方使用,每个地方都进行展开,代码量增大的同时,相对于每个展开的局部里多出了函数里的变量,空间变大。所以一般代码量大的或者是递归的函数不用inline。
2.系统对inline的看法:如果这个函数代码量大且是递归函数,则这个inline会被忽略。
3.inline的声明和定义不建议分离。
编译器需要在每个调用点都插入函数的代码。但是,链接器并不总是能够识别出这种情况并正确处理,特别是当
inline
函数的定义在多个编译单元(源文件)中可见时。
auto关键字(c++11)
auto可以根据赋值的变量推导出原本变量的类型。
#include <iostream>
using namespace std;
double Fun()
{
return 3.14;
}
int main()
{
int a = 10;
auto b = a;//auto 为 int
auto c = 'A';//auto 为 char
auto d = Fun();//auto 为 double(3.14)
//打印变量b,c,d的类型
cout << typeid(b).name() << endl;//打印结果为int
cout << typeid(c).name() << endl;//打印结果为char
cout << typeid(d).name() << endl;//打印结果为double
return 0;
}
auto并非是直接声明int b = a的,而是在编译时期,通过a的类型,给b 赋为int。就是相当于只是占着这个位置,之后再决定是什么。
auto指针和引用
对于一个指针来说:auto 或者 auto* 是等价的。auto& 则是自动推导引用
#include <iostream>
using namespace std;
int main()
{
int a = 10;
auto b = &a; //自动推导出b的类型为int*
auto* c = &a; //自动推导出c的类型为int*
auto& d = a; //自动推导出d的类型为int
//打印变量b,c,d的类型
cout << typeid(b).name() << endl;//打印结果为int*
cout << typeid(c).name() << endl;//打印结果为int*
cout << typeid(d).name() << endl;//打印结果为int
return 0;
}
auto引用一定要加&(不要犯浑) 不加不就又变成 auto d = a了吗?这样推出来的只是一个新的变量
同一行使用auto定义的多个变量
int main()
{
auto a = 1, b = 2; //正确
auto c = 3, d = 4.0; //编译器报错:“auto”必须始终推导为同一类型
return 0;
}
嫁鸡随鸡,嫁狗随狗。没有说人和狗结婚的。所以使用auto一行定义多个变量时,必须是同一类型。
auto用不了的场景
auto不能做参数
上面函数重载有说,c++在函数放进符号表时会重新起名字。如果你用的auto,那如果多个重载的函数全是auto参数,这个名字就乱套了,会出现重复。同时auto
关键字用于在编译时自动推导变量的类型,编译时我们函数的实现(即函数体)可能还没有被编写或者编译器还没有看到。
void Auto1(auto x)
{
//报错
return;
}
auto不能对数组使用
auto arr[10]; // 错误:auto不能用于声明数组大小
因为定义数组是需要大小的,而auto没有推导大小的功能
但是在我们定义好数组后是可以用auto去推导的。例如:
int data[10];
auto ptr = data; // 数组名是数组的头指针
auto& ref = data; // ref 是对 int[10] 的引用
ptr
被推导为 int*
类型,而 ref
仍然是对整个数组 data
的引用,但它的类型被自动推导为 int (&)[10]
。
范围for,auto和for循环的结合使用(c++11)
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//正常for循环打印数组中的所有元素
int n = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
//范围for打印数组中的所有元素
for (auto e : arr)
{
cout << e << " ";
}
for循环后的括号由冒号分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
简单点说就是 e 就是 arr[i],arr是这个数组。e在数组里从前往后走,走到最后一个
注意:与普通循环类似,可用continue来结束本次循环,也可以用break来跳出整个循环。
范围for的使用条件
1.必须有一个范围。
像上面的数组,它的范围就是下标 。如果你实现一个类,如果要使用范围for,你就得设计好begin和end。才有范围。
2.基本的符号。
设计好begin和end后。还得有++ == 等一系列的函数来支持范围for的遍历(迭代器)。
c++11的空指针
c++11以前,NULL的意义就是一个宏,NULL定义的就是一个0.这个0代表的意义对于c++11以前既可以字面常量0,也可能被定义为无类型指针(void*)的常量。就会出现下面指向不清的问题。
#include <iostream>
using namespace std;
void Fun(int p)
{
cout << "Fun(int)" << endl;
}
void Fun(int* p)
{
cout << "Fun(int*)" << endl;
}
int main()
{
Fun(0); //打印结果为 Fun(int)
Fun(NULL); //打印结果为 Fun(int)
Fun((int*)NULL); //打印结果为 Fun(int*)
return 0;
}
c++11以前想把NULL当做指针还得强转
c++11以后,引入了一个关键字nullptr。表示的就是(void*)NULL。空指针,而NULL依然是数字0
这样就可以做好区分了
标签:10,入门,int,auto,知识,C++,引用,指针,函数 From: https://blog.csdn.net/a1275174052/article/details/141959912