C++入门(未完待续)

1.命名空间
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员
①.普通的命名空间

namespace N1{//命名空间的内容,既可以定义变量,又可以定义函数
int a;
int Add(int left,int right){
return left+right;
}
}

②.命名空间可以嵌套

namespace N1{//命名空间的内容,既可以定义变量,又可以定义函数
int a;
int Add(int left,int right){
return left+right;
}
namespace N3{
int c;
int d;
int sub(int left,int right){
return left-right;
}

}
③.同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间

命名空间的使用
①.加命名空间名称和域作用限定符

int main(){
printf("%d",N::a);
return 0;
}

②.使用using将命名空间中成员引入

using N::b
int main(){
pritnf("%d",N::b);
return 0;
}

③.使用using namespace 命名空间名称引入
using namespace N;

using namespace N;//全部展开,全部授权
using N::add;//部分展开,部分授权

using namespace std;
std是c++标准库的命名空间
2.c++输入/输出
<<流插入运算符

流提取运算符

cout<<"hello world"<<endl;

endl为换行符

cout<<"hello world"<<"\n";

两者效果相同
使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含头文件以及std标准命名空间
使用c++输入输出更加方便,不需增加数据控制格式,比如整形"%d” 字符"%c"

注意cin的特点,与c语言中gets有些类似,gets是遇到换行符停止,而cin是遇到空格,tab或者换行符作为分隔符的,

char arr[20]={};
cin>>arr;

输入hello world 但由于中间存在空格,故arr这个数组中只有hello,没有world

3.缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值,在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

void func(int a=10){
cout<<a<<endl;
}
int main(){
func();//没有传参时,使用参数的默认值
func(10);//传参时,使用指定的实参
}

分类
全缺省参数
void func(int a=10;int b=20;int c=30){}
半缺省参数
void func(int a,int b=20;int c=30){}
注意①.半缺省参数必须从右往左依次来给,不能间隔着给
②.缺省参数不能在函数的声明和定义中同时出现
因为如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用哪个缺省值
声明不给定义给
③.缺省值必须是常量或者全局变量
//正确示例

int x = 3;//全局变量
void func(int a, int b = 2, int c = x)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

4.函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的
形参列表(参数个数类型顺序)必须不同,常用来处理实现功能类似数据类型不同的问题

#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(0,1) << endl;//打印0+1的结果
	cout << Add(1.1,2.2) << endl;//打印1.1+2.2的结果
	return 0;
}

注意:若仅仅只有返回值不同,其他都相同,则不构成函数重载。

c语言与c++类似,都要进行编译和链接两个过程
编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址实在b.o中
链接器看到a.o调用Add,但没有Add的地址,就会到b.o的符号表(符号表就是函数名,变量跟地址的映射)中去找Add的地址,然后链接到一起。面对多个Add函数,每个编译器有自己的修饰规则,在gcc下的修饰规则是:【_Z+函数长度+函数名+类型首字母】。
之所以c语言无法支持重载,是因为C编译器和C++编译器对函数名的修饰不同

所以,返回值的不会构成函数重载,因为修饰规则并不会受返回值的影响。
如果函数名修饰规则带入返回值,返回值能否构成重载?

不能,因为不知道要调用谁
5.引用
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它
引用的变量共用同一块内存空间。
类型& 引用变量名(对象名) = 引用实体;
int a=10;
int&b=a; //相当于给a取了一个别名,给b引用了a,同一个变量取了个名字
b=3;//改变b也就相当于改变a了
引用类型必须和引用实体是同种类型的
引用的特性
①.引用在定义时必须初始化

//正确示例
int a = 10;
int& b = a;//引用在定义时必须初始化
//错误示例
int a = 10;
int &b;//定义时未初始化
b = a;

②.一个变量可以有多个引用

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

③.引用一旦引用了一个实体,就不能再引用其他实体(c++的引用不能改变指向)

int a = 10;
int& b = a;
int c = 20;
b = c;//错误,b已经引用了a,就不能在引用c
实际效果变为,b的值变为20,而因为a与b相同,故a的值也变成了20

常引用
在引用的过程中,权限可以平移,可以缩小,但不能放大

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

会出现错误,相当于权限的放大,因为在这里a为常量,如果用int&b=a;那么,b可以修改,但a不可以修改

const int&ra=a;

相当于权限的平移,不会出错

int&b=10;

会出现错误,b为常量,需要改为

const int&b=10;
int i=0;
double&d=i;

会出现错误,因为发生类型转换时会产生一个double类型临时变量,临时变量具有常性
(int到double存在隐式类型的提升,而在提升的过程中系统会创建一个常量区来存放a类型提升后的结果)
需要改为

int i=0;
const double&d=i;
int func(){
int a=0;
return a;
}
int&ret=func();

会出现问题,因为func的返回值是a的一份临时拷贝,临时变量具有常性
需改成

const int&ret=func();

引用的使用场景
①.做参数

void swap(int&left,int&right){
int temp=left;
left=right;
right=temp;
}

②.做返回值
当然引用也能做返回值,但是要特别注意,我们返回的数据不能是函数内部创建的普通局部变量,因为在函数内部定义的普通的局部变量会随着函数调用的结束而被销毁。我们返回的数据必须是被static修饰或者是动态开辟的或者是全局变量等不会随着函数调用的结束而被销毁的数据。

int& count(){
	int n=0;
	n++;
	return n;
} 
int main(){
	int ret=count();
	return 0;
}

count函数返回n的别名,但是n已经销毁,如果栈帧没有清楚,那么结果为1,否则结果为随机值

#include<iostream>
using namespace std;
int&Add(int a,int b){
	int c=a+b;
	return c;
}
int main(){
	int&ans=Add(1,2);
	Add(3,4);
	cout<<ans<<endl;
}

但如果再加上一句cout<<ans<<endl;打印出的结果将变为随机值
因为在第一次调用cout<<ans<<endl时,函数传参建立栈帧,调用完后被覆盖,第二次调用时已经被覆盖,为随机值

#include<iostream>
using namespace std;
int& Add(int a, int b)
{
	static int c = a + b;
	return c;
}

int main()
{
	int& ans = Add(1,2);
	Add(3, 4);
	cout << ans << endl;
}

为什么会出现随机值,因为你在函数里定义的变量是临时变量,出了函数函数是会销毁的,这时它就随机指向内存中的一块空间了。所以在引用做函数返回值时最好还是给在函数中定义的变量加上static。加上static后,结果将变为3。
因为第二次调用Add函数时,static int c=a+b;并不会执行,而是直接return c;
但如果将该函数修改为

int& Add(int a, int b)
{
	static int c ;
	c=a+b;
	return c;
}

总结:
传引用传参(任何时候都可以)
①.提高效率
②.输出型参数(形参的修改影响实参)
传引用返回(出了函数作用域对象还在才可以用)
①.提高效率
②.修改返回对象

顺序表查找和修改可以用一个函数来解决

int& SLAT(struct seqlist&ps,int i){
return ps.a[i];
}

想要修改SLAT(s,0)=1;

引用和指针的区别(两者的差别主要是在语法检查层面,在底层实现上并无差别)
①、引用在定义时必须初始化,指针没有要求。
②、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
③、没有NULL引用,但有NULL指针。
④、在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
⑤、引用进行自增操作就相当于实体增加1,而指针进行自增操作是指针向后偏移一个类型的大小。
⑥、有多级指针,但是没有多级引用。
⑦、访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理。
⑧、引用比指针使用起来相对更安全。

7.内联函数

宏的优点
①.一定程度上兼容不同类型,没有严格的类型限制
②.针对频繁调用小函数,不再需要建立栈帧,提高了效率
缺点
①.不方便调试宏(预编译期间进行了替换)
②.导致代码可读性差,可维护性差,容易误用
③.没有类型安全的检查

例:
关于宏 如果#define add (A,B) (A+B) 是错误的
因为如果出现add(a&b,a|b),由于+的优先级大于&和|,所以会先执行+
改为#define add(A,B) (A)+(B)

以inline函数修饰的函数叫做内联函数,编译时c++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的速率
①.inline是一种以空间换时间的做法,省去调用函数的开销。所以代码很长/递归的函数不适宜
使用作为内联函数。
②.inline对于编译器而言只是一个建议(内联说明只是向编译器发送一个请求,编译器可以忽略这个请求),编译器会自动优化,如果定义为inline的函数体内代码比较长/递归等
等,编译器优化时会忽略掉内联。
例: Func 100行,在100个位置调用
如果Func是内联函数,那么func的指令为100x100=10000行
如果Func不是内联函数,那么func的指令为本身的100行,和100个call调用指令
代码膨胀
③.inline不建议声明和定义分离,分离会导致链接错误(另一个文件调用该函数需要地址)。因为inline被展开,就没有函数地址了(可以理解为内联函数在符号表中没有地址),链接就会找不到。

8.auto关键字auto

int a=0;
auto b=a;//根据右边的值自动推导左边的值
auto c=&a;

注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类
型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为
变量实际的类型。

①.auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
②.在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对
第一个类型进行推导,然后用推导出来的类型定义其他变量。
auto c=3,d=4.0;//该行代码会编译失败,因为c和d的初始化表达类型不同

auto不能推导的场景
①.auto做为函数的参数
②.auto不能直接用来声明数组

基于范围的for循环
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中
引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量
第二部分则表示被迭代的范围
范围for一般结合auto去用,不管数组是什么类型

for(auto e:array)

取array里的值依次赋给e,e的值的改变并不会改变array里面的值,要想修改必须用引用

for(auto&e:array){
e*=2;
}

①.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的
方法,begin和end就是for循环迭代的范围,以下代码就有问题,因为for的范围不确定

void testfor(int array[]){
for(auto&e:array){
cout<<e<<endl;
}
}

10.指针空值nullptr
NULL其实是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL    0
#else  
#define NULL    ((void *)0)
#endif  
#endif  

可以看到,NULL可能被定义为字面常量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;
}

程序本意本意是想通过Fun(NULL)调用指针版本的Fun(int* p)函数,但是由于NULL被定义为0,Fun(NULL)最终调用的是Fun(int p)函数。

注:在C++98中字面常量0,既可以是一个整型数字,也可以是无类型的指针(void*)常量,但编译器默认情况下将其看成是一个整型常量,如果要将其按照指针方式来使用,必须对其进行强制转换。

C++11中的指针空值
对于C++98中的问题,C++11引入了关键字nullptr。
在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的。
在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同,大小都为4。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值