关于C++:各种常量
本小白了解C++中const关键字的用法,特在此做个笔记
今天在助教工作中遇到同学提出的一些问题,认为非常有意义且重要,特此来整理一下所学
const在面向过程编程时的用法:
修饰基本变量使之成为不可改动的常量:
#include <iostream>
using namespace std;
int main() {
const int a = 5;
// a=10;//此句编译不通过,错误提示:表达式必须是可修改的左值
cout << a;
return 0;
}
输出:
5
这种用法必须在定义变量时初始化,而不可以在之后赋初值:
const int a;//错误:常量 变量 "a" 需要初始值设定项
a = 10; //错误:表达式必须是可修改的左值
修饰指针:
#include <iostream>
using namespace std;
int main(void) {
int num;
const int *p1 = #
// p1可变,*p1不可变,此时不能用*p1来修改,但是p1可以转向
int *const p2 = #
// p2不可变,*p2可变,此时允许*p2来修改其值,但是p2不能转向。
const int *const p3 = #
// p3不可变,*p3也不可变,此时既不能用*p3来修改其值,也不能转向
return 0;
}
修饰函数返回值:
事实上,修饰函数返回值并非没有什么卵用:
非指针返回值:
对非指针返回值确实没有什么卵用:
#include <iostream>
using namespace std;
const int fun(int a){
const int b=a;
return b;
}
int main() {
int b=fun(10);
b=100;
cout<<b;
return 0;
}
输出:
100
为啥呢,实际上函数的调用这个式子本身就是个常量:
#include <iostream>
using namespace std;
const int fun1(int a){
const int b=a;
return b;
}
int fun2(int a){
const int b=a;
return b;
}
int main() {
int b=fun1(10)=100;//错误:表达式必须是可修改的左值
int b=fun2(10)=100;//错误:表达式必须是可修改的左值
cout<<b;
return 0;
}
这里使用了连续赋值从右到左依次赋值的语法,可以见到,fun2(10)=100本身就不通过。
也对,人家函数辛辛苦苦得到的结果,凭什么能让你随便改。
指针返回值:
修饰指针指向内存地址也和上面一样没什么卵用:
#include <iostream>
using namespace std;
int *const fun(int a) {
int *const pa = new int(a);
return pa;
}
int main() {
int *b = fun(10);
*b = 100;
cout << *b;
return 0;
}
输出:
100
但是修饰指向值就有用起来了:
#include <iostream>
using namespace std;
const int * fun(int a) {
const int* pa = new int(a);
return pa;
}
int main() {
int *b = fun(10);//错误:"const int *" 类型的值不能用于初始化 "int *" 类型的实体
*b=100;
cout << *b;
return 0;
}
这就让我们不能偷鸡摸狗的通过返回的地址瞎搞了。
修饰引用:
void fun(testclass &a){
a.testvar3+=5;//正确
}
void fun(testclass const &a){
a.testvar3+=5;//错误:表达式必须是可修改的左值
}
可以见到,const修饰的引用不可以进行更改。
const在面向对象编程时的用法:
首先是实现个实验类:
#include <iostream>
using namespace std;
class testclass {
public:
string name;
const int testvar1;
const int testvar2 = 5;
int testvar3;
testclass(string in_name, int in_1, int in_2 = 20)
: name(in_name), testvar1(in_1), testvar3(in_2) {}
void output(void) {
cout << "<" << name << " is a general member>\t"
<< "var1:" << testvar1 << ' ' << "var2:" << testvar2 << ' '
<< "var3:" << testvar3 << endl;
}
void output(void) const {//这写法一会再说
cout << "<" << name << " is a const member>\t"
<< "var1:" << testvar1 << ' ' << "var2:" << testvar2 << ' '
<< "var3:" << testvar3 << endl;
}
};
int main(void) {
testclass class_a("class_a", 10);
const testclass class_b("class_b", 20);
class_a.output();
class_b.output();
return 0;
}
输出:
<class_a is a general member> var1:10 var2:5 var3:20
<class_b is a const member> var1:20 var2:5 var3:20
完美,接下来看——
常成员:
如下的两句定义了两个常成员变量:
const int testvar1;
const int testvar2 = 5;
常成员变量无法在初始化后被修改:
class_a.testvar1=20;//错误:表达式必须是可修改的左值
class_a.testvar2=10;//错误:表达式必须是可修改的左值
而类的常成员变量如何初始化呢?请看类内的代码:
testclass(string in_name, int in_1, int in_2 = 20)
: name(in_name), testvar1(in_1), testvar3(in_2) {}
我们使用了构造函数的初始化列表以初始化常成员变量,而不是在构造函数内,这意味着构造函数的确是先建立变量的内存空间,在执行构造函数的语句块。
而使用初始化列表可以初始化常成员变量,这说明了初始化列表在执行构造函数的语句块前执行,即在建立变量的同时。
好,我们搞明白了常成员变量,那么常成员函数呢?
请想一下,“常”指的是什么?是常量。那么函数是啥,是操作。
那么常成员函数又是什么应该不难想到,就是不修改数据的操作呗,来看:
void change(int in_) const { testvar3 = in_; }//错误:表达式必须是可修改的左值
testvar3明明声明是一个变量,为森马会提示说它不能改呢?
原因在于const修饰的成员函数把所有的成员变量都视为常量。
在这里,const关键字是函数类型的一个组成部分,因此在实现部分也要带关键字:
void fun(int in_) const; //声明
void fun(int in_) const {...} //实现
是故const关键字可以用于对重载函数的区分。
常对象:
如下的一句声明了一个常对象:
const testclass class_b("class_b", 20);
对于常对象,我们无法对他的值进行修改,
class_b.testvar3=6;//错误:表达式必须是可修改的左值
对于非常量对象,可以调用它的常量与非常量函数,但对于常量对象,只能调用它的常成员函数:
testclass class_a("class_a", 10);
const testclass class_b("class_b", 20);
class_a.output();
class_b.output();
输出:
<class_a is a general member> var1:10 var2:5 var3:20
<class_b is a const member> var1:20 var2:5 var3:20
可以看到我们output函数有两个重载:
void output(void) {
cout << "<" << name << " is a general member>\t"
<< "var1:" << testvar1 << ' ' << "var2:" << testvar2 << ' '
<< "var3:" << testvar3 << endl;
}
void output(void) const {
cout << "<" << name << " is a const member>\t"
<< "var1:" << testvar1 << ' ' << "var2:" << testvar2 << ' '
<< "var3:" << testvar3 << endl;
}
对于非常量对象,我们可以运行他的两种函数,但对与以上这种重载,我们优先运行他的非常量版本,而对于常对象,我们只能运行他的常量成员函数。
觉得还行的看官点个攒再走吧…