CPlusPlusThings/basic_content/const/README.md
Light-City 8edbbbc5a2 update
2019-11-05 16:56:07 +08:00

405 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 关于作者
微信公众号:
![](../img/wechat.jpg)
## 1.const含义
常类型是指使用类型修饰符**const**说明的类型,常类型的变量或对象的值是不能被更新的。
## 2.const作用
1可以定义常量
```c++
const int a=100;
```
2类型检查
const常量与#define宏定义常量的区别const常量具有类型编译器可以进行安全检查#define宏定义没有数据类型只是简单的字符串替换不能进行安全检查。
3防止修改起保护作用增加程序健壮性
```c++
void f(const int i){
i++; //error!
}
```
4可以节省空间避免不必要的内存分配
const定义常量从汇编的角度来看只是给出了对应的内存地址而不是像#define一样给出的是立即数所以const定义的常量在程序运行过程中只有一份拷贝而#define定义的常量在内存中有若干个拷贝。
## 3.const对象默认为文件局部变量
<p><font style="color:red">注意非const变量默认为extern。要使const变量能够在其他文件中访问必须在文件中显式地指定它为extern。</font></p>
> 未被const修饰的变量在不同文件的访问
```c++
// file1.cpp
int ext
// file2.cpp
#include<iostream>
/**
* by 光城
* compile: g++ -o file file2.cpp file1.cpp
* execute: ./file
*/
extern int ext;
int main(){
std::cout<<(ext+10)<<std::endl;
}
```
> const常量在不同文件的访问
```c++
//extern_file1.cpp
extern const int ext=12;
//extern_file2.cpp
#include<iostream>
/**
* by 光城
* compile: g++ -o file const_file2.cpp const_file1.cpp
* execute: ./file
*/
extern const int ext;
int main(){
std::cout<<ext<<std::endl;
}
```
<p><font style="color:red">小结可以发现未被const修饰的变量不需要extern显示声明而const常量需要显示声明extern并且需要做初始化因为常量在定义后就不能被修改所以定义时必须初始化。</font></p>
## 4.定义常量
```c++
const int b = 10;
b = 0; // error: assignment of read-only variable b
const string s = "helloworld";
const int i,j=0 // error: uninitialized const i
```
上述有两个错误第一b为常量不可更改第二i为常量必须进行初始化(因为常量在定义后就不能被修改,所以定义时必须初始化。)
## 5.指针与const
与指针相关的const有四种
```c++
const char * a; //指向const对象的指针或者说指向常量的指针。
char const * a; //同上
char * const a; //指向类型对象的const指针。或者说常指针、const指针。
const char * const a; //指向const对象的const指针。
```
小结:如果*const*位于`*`的左侧则const就是用来修饰指针所指向的变量即指针指向为常量如果const位于`*`的右侧,*const*就是修饰指针本身,即指针本身是常量。
具体使用如下:
1指向常量的指针
```c++
const int *ptr;
*ptr = 10; //error
```
ptr是一个指向int类型const对象的指针const定义的是int类型也就是ptr所指向的对象类型而不是ptr本身所以ptr可以不用赋初始值。但是不能通过ptr去修改所指对象的值。
除此之外也不能使用void`*`指针保存const对象的地址必须使用const void`*`类型的指针保存const对象的地址。
```c++
const int p = 10;
const void * vp = &p;
void *vp = &p; //error
```
另外一个重点是:**允许把非const对象的地址赋给指向const对象的指针**。
将非const对象的地址赋给const对象的指针:
```c++
const int *ptr;
int val = 3;
ptr = &val; //ok
```
我们不能通过ptr指针来修改val的值即使它指向的是非const对象!
我们不能使用指向const对象的指针修改基础对象然而如果该指针指向了非const对象可用其他方式修改其所指的对象。可以修改const指针所指向的值的但是不能通过const对象指针来进行而已如下修改
```c++
int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;
```
<p><font style="color:red">小结:对于指向常量的指针,不能通过指针来修改对象的值。<br/>也不能使用void`*`指针保存const对象的地址必须使用const void`*`类型的指针保存const对象的地址。<br/>允许把非const对象的地址赋值给const对象的指针如果要修改指针所指向的对象值必须通过其他方式修改不能直接通过当前指针直接修改。</font></p>
2常指针
const指针必须进行初始化且const指针的值不能修改。
```c++
#include<iostream>
using namespace std;
int main(){
int num=0;
int * const ptr=&num; //const指针必须初始化且const指针的值不能修改
int * t = &num;
*t = 1;
cout<<*ptr<<endl;
}
```
上述修改ptr指针所指向的值可以通过非const指针来修改。
最后当把一个const常量的地址赋值给ptr时候由于ptr指向的是一个变量而不是const常量所以会报错出现const int* -> int *错误!
```c++
#include<iostream>
using namespace std;
int main(){
const int num=0;
int * const ptr=&num; //error! const int* -> int*
cout<<*ptr<<endl;
}
```
上述若改为 const int *ptr或者改为const int *const ptr都可以正常
3指向常量的常指针
理解完前两种情况下面这个情况就比较好理解了
```c++
const int p = 3;
const int * const ptr = &p;
```
ptr是一个const指针然后指向了一个int 类型的const对象。
## 6.函数中使用const
> cost修饰函数返回值
这个跟const修饰普通变量以及指针的含义基本相同
1const int
```c++
const int func1();
```
这个本身无意义因为参数返回本身就是赋值给其他的变量
2const int*
```c++
const int* func2();
```
指针指向的内容不变。
3int *const
```c++
int *const func2();
```
指针本身不可变
> const修饰函数参数
1传递过来的参数及指针本身在函数内不可变无意义
```c++
void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变
```
表明参数在函数体内不能被修改但此处没有任何意义var本身就是形参在函数内不会改变。包括传入的形参是指针也是一样。
输入参数采用“值传递”由于函数将自动产生临时变量用于复制该参数该输入参数本来就无需保护所以不要加const 修饰。
2参数指针所指内容为常量不可变
```c++
void StringCopy(char *dst, const char *src);
```
其中src 是输入参数dst 是输出参数给src加上const修饰后如果函数体内的语句试图改动src的内容编译器将指出错误这就是加了const的作用之一
3参数为引用为了增加效率同时防止修改
```c++
void func(const A &a)
```
对于非内部数据类型的参数而言象void func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A 类型
的临时对象用于复制参数a而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率可以将函数声明改为void func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临
时对象。但是函数void func(A &a) 存在一个缺点:
“引用传递”有可能改变参数a这是我们不期望的。解决这个问题很容易加const修饰即可因此函数最终成为
void func(const A &a)。
以此类推是否应将void func(int x) 改写为void func(const int &x),以便提高效率?完全没有必要,因为内部数
据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
<p><font style="color:red">小结对于非内部数据类型的输入参数应该将“值传递”的方式改为“const 引用传递”目的是提高效率。例如将void func(A a) 改为void func(const A &a)。<br/>对于内部数据类型的输入参数不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的又降低了函数的可理解性。例如void func(int x) 不应该改为void func(const int &x)。</font></p>
以上解决了两个面试问题:
1如果函数需要传入一个指针是否需要为该指针加上const把const加在指针不同的位置有什么区别
2如果写的函数需要传入的参数是一个复杂类型的实例传入值参数或者引用参数有什么区别什么时候需要为传入的引用参数加上const。
## 7.类中使用const
在一个类中任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时不慎修改
数据成员或者调用了其它非const成员函数编译器将指出错误这无疑会提高程序的健壮性。使用const关
字进行说明的成员函数称为常成员函数。只有常成员函数才有资格操作常量或常对象没有使用const关键字
明的成员函数不能用来操作常对象。
对于类中的const成员变量必须通过初始化列表进行初始化如下所示
```c++
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
};
Apple::Apple(int i):apple_number(i)
{
}
```
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
例如
```c++
//apple.cpp
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};
//main.cpp
#include<iostream>
#include"apple.cpp"
using namespace std;
Apple::Apple(int i):apple_number(i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}
//编译 g++ -o main main.cpp apple.cpp
//结果
take func 1
2
take func 10
take func 100
```
上面getCount()方法中调用了一个add方法而add方法并非const修饰所以运行报错。也就是说const对象只能访问const成员函数。
而add方法又调用了const修饰的take方法证明了非const对象可以访问任意的成员函数,包括const成员函数。
除此之外我们也看到add的一个重载函数也输出了两个结果说明const对象默认调用const成员函数。
我们除了上述的初始化const常量用初始化列表方式外也可以通过下面方法
第一将常量定义与static结合也就是
```c++
static const int apple_number
```
第二在外面初始化
```c++
const int Apple::apple_number=10;
```
当然如果你使用c++11进行编译直接可以在定义出初始化可以直接写成
```c++
static const int apple_number=10;
或者
const int apple_number=10;
```
这两种都在c++11中支持
编译的时候加上`-std=c++11`即可!
这里提到了static下面简单的说一下
在C++static静态成员变量不能在类的内部初始化在类的内部只是声明定义必须在类定义体的外部通常在类的实现文件中初始化
在类中声明
```c++
static int ap;
```
在类实现文件中使用:
```c++
int Apple::ap=666
```
对于此项c++11不能进行声明并初始化也就是上述使用方法