c++引用与常量
一·常量表达式和constexpr与const
常量表达式概念:值不会改变并且在编译过程中就能计算出结果的表达式。
ps:constexpr int a=20;
constexpr int b=20+a;
constexpr必须用常量表达式初始化,也就是说必须在编译过程就能计算出结果(若要用函数作为constexpr的初始值那么该函数应该是constexpr类型的函数)。
constexpr函数必须满足下述限制:
函数返回值不能是void类型 函数体不能声明变量或定义新的类型
函数体只能包含声明、null语句或者一条return语句
在形参实参结合后,return语句中的表达式为常量表达式
const 不要求在编译过程中就能计算出结果 (强调运行时不可改变) 大多数情况下并没有区别。
const限定指针和引用。
指向const对象的指针 const type 指针
const int a=1; //const地址无法赋给非const指针所以应该定义一个指向const的指针
const int * b=&a; //指向常量的指针,而非常指针。
const int c=2;
b=&c; //指向对象可以改变,指向对象内容不能改变。
int d=10;
b=&d; //也可以将一个非const地址赋给 指向const的指针 但是不能通过该指针改变该变量的内容。
*b=15;//错误
指向非const对象地const指针(常指针 ) type const 指针
int num =10;
int * const poi =#// type const 指针 指针的值初始化后不能改变指向固定的单元(只能指一次)
*poi=99;//const限定的是poi的值;可以对指向内容修改,但不可以对指针指向对象修改。
int member=10;
poi=&member;//错误
指向const对象的const指针
/*const type const 指针(第一个const限定int表示指针指向的单元是常量,
第二个const限定‘指针’表示指针的值也是常量,因此该指针所在的内存值不允许改变它所指向内存的值也不能改变)*/
int demo=10;
const int *const pi=&demo;对象及内容都不能改变
const限定引用,与const指针相似
const int a=1;
int 7b=a;// const type &引用名(不能用非const引用指向const对象)
const int& b=a;//right
b=10;//错误,const引用不能用来修改他所绑定的对象。
a=10;//const引用仅对自己可参与的操作进行了限定对所指向的对象本身是不是常量未作限定。因为指向的对象也可能不是const,所以允许通过其他途径改变它的值。
const int &c=a*10;//可以用任意表达式初始化const引用,只要表达式的结果能转换成引用的类型即可。
二.auto和decltype
由auto声明变量的类型由编译器去自动分析表达式的类型,推断出变量的实际类型(很好用)
auto x=5
auto x=1,c=3.14;//一个int 一个double错!当auto后定义多个变量时类型要一致
auto poi=Set.begin()//很好用,省了set<type>::iterator
decltype 可以通过表达式推断出定义的变量的类型但是不用该表达式初始化。
int i = 4;
decltype(i) a; //推导结果为int。a的类型为int。
三.new和delete
new在对上动态分配空间创建对象并返回对象的地址,一般将返回的地址保存在指针变量中,以便间接访问对上的对象
int *pi=new int;
int *poi=new int(1000);
/* new表达式的形式
1.分配单个对象new 类型,new 类型(初始值)
2.分配多个连续存储的对象 new 类型[数组大小]
3.定位new(将对象创建在已经分配好的内存中) new (指针) 类型;*/
堆上的空间在使用后必须释放否则会造成内存泄漏,
delete pi;
delete poi[];
/*new分配的空间用delete运算符释放。
1释放 new分配的单个对象 delete 指针;
2释放new分配的数组形式 delet[] 指针;
3 定位new没有对应的形式;*/
执行delete运算后指针指向的空间被释放归还给自由空间不能再使用指针所指向的内容,但是指针自己的储存空间还是存在的。该指针被称为空悬指针指向不确定的单元如果在继续使用该指针间接使用这个单元就是非法的对自由空间造成损害进而引起不可预料的错误。
四.引用
左值与右值
区别 | 左值 | 右值 |
---|---|---|
赋值表达式 | 出现在赋值号左边 | 在赋值号右边的 |
地址与名字 | 可以取地址的有名字 | 不能取地址的没有名字 |
生成的表达式 | 返回左值引用的函数 赋值 下标 解引用和前缀自增自减运算符 | 返回非引用类型的函数 连同算术、关系、位运算、后缀自增自减运算符、字面值常量、要求转换的表达式。 |
左值引用 (定义 类型 &引用名=目标变量名)
int a=100;
int &b=a;/*&是标识符(引用必须被初始化并且初始化完成之后引用将会和初始值对象一直绑定在一起)。*/
b=10;//又称别名,它可以作为对象的另一个名字,通过引用可以间接的操纵对象,对引用的操作和对对象的直接操作一样。
int *poi=&a;
int *pi=&b;//此时a==b
/* 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。引用本身不是对象所以不能定义引用的引用。*/
右值引用(必须要绑定到右值的引用)定义 类型 &&右值引用变量=右值表达式;
int &&a=10;//正确
int &&b=10*5;//正确10*5是右值
int &&c=a;//a的类型是int是左值
调用标准库中定义的函数std::move() move()函数返回给定对象的右值引用, 可以显式的将一个左值转换为对应的右值引用类型。
int &&c=move(a);//但是过程是销毁a,a的右值由c接管。此后不能使用a,但可以给它赋新右值。
五.文件的输入输出
文件的读写
如果想以输入方式打开,就用ifstream来定义;
如果想以输出方式打开,就用ofstream来定义;
如果想以输入/输出方式来打开,就用fstream来定义
ofstream demo("demotest.txt"); 可以分成两步
demo<<"demoout";
demo.close(); //完成操作后要关闭文件
ifstream demo("test.txt");
char dchar;
demo>>dchar;//从文件中读取一个字符
char dworld[80];
demo>>dworld;//从文件中读取一个单词
demo.getline(dworld,80);//从文件中读取一行
string line;
getline(demo,line);//读取一行转换成字符串
fin.close();
区别
ifstream ifile;
ofstream ofile;
ofile<<"I Love You";//向文件写入字符串"I Love You"
int i;
ifile>>i;//从文件输入一个整数值。
字符串流stringstream是 C++ 提供的一个字符串流(stream),和iostream、fstream有类似的操作方式
istringstream 从string中读取数据 定义 istringstream is(strs);
ostringstream 向string对象写入格式化的内容。
stringstream 即可以读取也可以写入。
stringstream ss;
ss << "hello ";
ss << "world!"; // 对stringstream而言,operator<< 是一直往字符串流中写字符,而不是覆盖之前输入的字符
cout << ss.str() <<endl;
/*str()函数创建的是一个临时的string对象这个string对象在函数str()语句结束就会被销毁,一般使用时应先声明一个string对象s,将str()赋值给sconst string s=ss.str();或者const string& s=ss.str();这样就“延长了临时变量ss.str()的生命周期”,使得ss.str()生命结束时刻和s一样*/
与容器一样流不会降低自己空间,即使用了ss.clear()😭 清空操作如下 😦
ss.clear();//清空流
ss.str("");
六.range -for
for(int ele :{2,3,44,5,6,77})
{
cout<<ele<<endl;
}
int arr []={1,2,3,4}
for(int ele :arr)
{
cout<<ele<<endl;
}
vector<int > demo={1,2,3,4};//结合auto关键字可以方便地遍历STL容器
for(auto ele:demo)
{
cout<<ele<<endl<<;
}
for(auto& ele:demo) 如果需要修改其中元素可以引用;
{
ele+=2;
}
切记!!!!!
不能在遍历容器时,改变容器的Size,即增删元素。
七.函数
实参一定是确定值 不需要类型!!!
1.函数的参数
1)形参和实参
形参:在定义函数的时候,函数名后面小括号中的参数 , 格式: 数据类型 变量 如:int x;
形参的作用域:只能在本函数中使用
实参:调用函数的时候传递的参数
2)参数的传递的过程
实参的值拷贝一份放到函数形参中
3)函数传参有三种传参方式:传值、传址、传引用
①按值传递
ⅰ形参和实参各占一个独立的存储空间。
ⅱ形参的存储空间是函数被调用时才分配的,调用开始,系统为形参开辟一个临时的存储区,然后将各实参传递给形参,这是形参就得到了实参的值。
②地址传递
地址传递与值传递的不同在于,它把实参的存储地址传送给形参,使得形参指针和实参指针指向同一块地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
③引用传递
引用传递是以引用为参数,则既可以使得对形参的任何操作都能改变相应数据,又使函数调用方便。引用传递是在形参调用前加入引用运算符“&”。引用为实参的别名,和实参是同一个变量,则他们的值也相同,该引用改变则它的实参也改变。
2.函数的返回值
概念:执行函数体中的程序段,最后获取的值并返回给主调函数,函数的返回值只能通过return 关键字进行返回
格式:return 表达式;/ return (表达式);
返回值类型要与返回值相同。
是否要定义形参看是否有未知内容参与运算,调用时实参必须对应.参数传递的是值。
函数中可以有多个return ,但是只有一个起作用,因为函数会结束后会带回一个值。
函数调用和返回
函数调用会使程序的控制权传递给被调函数而当前活动会被挂起。
当前函数执行完成后主函数从调用语句之后的语句恢复执行。
函数在执行完函数体的最后一条语句或或遇到return语句时返回。
返回类型和return语句
return 语句形式
return;
return表达式;
非void函数必须返回一个与声明类型匹配的值否则会引起编译错误。
返回值 默认情况下,函数的返回值是按值传递的,得到控制权的函数将接受return语句中指定的表达式值得副本。
返回引用(我觉得特别重要)
函数声明为返回引用,则不需要对return语句中的表达式进行复制,而是返回对象本身。
函数返回引用仅是它所指向对象的一个别名。
//找出s1和s2中比较短的一个并返回其引用
const string& shorter(const string& s1, const string& s2)
{
return (s1.size() <= s2.size()) ? s1 : s2;
}
//函数返回结果时不会真正复制对象,返回的就是s1或s2本身。
string a=hello;
string b=word;
shorter(a,b)=“Hello”;//返回值为引用。
cout<<a<<' '<<b;//Hello word;
注:对引用返回值的修改会改变实际返回的对象,为了避免这种情况,可以将返回值声明称const。
不能返回自动局部对象的指针或引用:函数执行结束后,函数占用的栈存储空间被释放,原本位于这段存储空间中的局部对象和临时变量都被释放,返回的局部对象引用或指针指向不再有效的内存区域
重载函数
如果同一个作用域内的几个函数名字相同但形参列表不同,则他们是重载函数
形参列表不同的概念:
1.形参数量不同
2.形参类型不同
3.常指针与指针不同,常引用与引用不同。
调用函数时如果存在多个重载函数,编译器将根据函数调用中指定的实参进行选择。
存储类别 static静态存储
static对象在控制流程第一次到达其定义点时被初始化,如果没有提供初始值就被自动初始化为0;
在函数的后续调用中,初始化语句被跳过
静态对象的值在函数被多次调用之间保持有效,生存期会延续到整个程序结束但他的作用于仍然是局部的,因此需要在同一函数的两次调用之间保留某些数据时可以使用局部static对象。
八.指针
附博客 https://mp.csdn.net/mdeditor/86935527#
https://mp.csdn.net/mdeditor/87398537#