一、顶层const和底层const
修饰的是带有指针类型的变量。
这里我们先区分下要讲的概念:
int a = 10;
int *p = &a;
指针本身是指p(值为&a),指针指向的对象是指*p(值为a)
你可以这样理解顶层:就是最顶端,最原始的东西,一个变量有一个值和地址,值是通过地址得到的,所以,地址在顶层,而const是说常量,那么合起来,顶层const
就是地址是一个常量,那么底层const
就是说值是一个常量
顶层const:
int a = 10;
int * const p = &a;
- 修饰的是指针本身(也就是p),表示地址不可以修改。也就是说不能修改p的指向(p的值始终只能是&a),但是可以修改p指向对象的值,也就是修改*p(也就是a)的值。
// 接上
int b = 15;
p = &b; // 这里是错误的,p是顶层const,不能修改
*p = b; // 这里是正确的,修改的是*p的值,也就是指针指向的对象的值。
底层const:
int a = 10;
const int* p = &a; // int const* p = &a; 都是正确的。
修饰的是值,表示指针指向的对象的值不可以修改。*p(也就是a)的值不能修改。
区分方法:
最简单的区分方法是看const
关键字的位置:
①int * const p
放在*
右边就是修饰p
,修饰的是地址,就是顶层const
②int const *p
或 const int *p
放在*
左边,表示修饰的是*p
,修饰的是值,就是底层const
二、指针和引用
引用:并非对象,是对另一个已经存在的对象起的另外一个名字。
指针:是一个对象,指向一种类型的复合类型。
这里最大的区别在于,引用不是对象,而指针是对象,怎么去理解呢?就是指针可以获取到指针本身的地址,例如int *a
; &a
是指针本身的地址值,而a
是指针所指的对象的地址。而引用不一样,它本身没有地址,它的地址为引用的对象的地址。
这里会有这样几个现象:
①指向指针的指针:
int *a; // a是一个对象(指针本身是对象)
int* *b = &a; // 正确的,b是指向一个指针(int*)的指针
对于指针来说,指针本身是一个对象,是有地址的。所以可以把指针本身看作一个对象,这时候用其他的指针指向这个对象,这是没有问题的,这里无论套多少层都是可以看成指向指针的指针。
②指向引用的指针(不存在的,是错误的,不要以为是对的!)
int a = 10;
int &b = a;
int &*p = &b; // 错误!相当于 int &(*p) = &b;
我们知道引用只是给其他变量取的一个别名,那么本身是不应该存在有地址一说的,*p是个指针,本身就具备地址,就与引用产生了矛盾,所以这里是不成立的。
三、引用的一些用法:
①允许常量引用绑定非常量的对象,反之则错误
int a = 10;
const int &b = a; // 正确
const int &c = 10;
int d = c; // 错误
我们进行类型转换的时候,我们可以将高精度、高容量向低精度、低容量直接转化。所以这里只能将非常量直接转为常量
四、char类型
①char类型的定义:
先说下char
,这是c风格字符串,是一种约定俗成的写法,字符串及字符数组都要以空字符结尾(‘\0’)。也就是说我们定义char* str = “123”;这个字符串后面会存在一个空字符。(注意string类型不一样,string类型没有结尾的’/0’)
char* str = "123";
cout << sizeof(str) << endl; // 这里输出的值为4,因为str的内容实际为:{'1' '2' '3' '\0'}
②char类型的输出:
char a = 's';
cout << a << endl; // 输出 s
cout << &a <<endl; // 错误,出现不确定值
单字符输出的情况,是直接输出字面值。如果采用地址输出的话,就会变成乱码,因为c风格字符串默认以'/0'
结尾,它会不断读取直到出现'\0'
为止,所以基本输出都是乱码,不排除运气好,正好后面出现了结束符,这样可能没有乱码。
char *s = "hello"; // 会有警告,由于低容量转化为高容量,正确无警告:const char* s = "hello";
char s2[] = "hello";
cout << *s << " " << *s2 << endl; // 输出 h h
cout << s << " " << s2 << endl; // 输出hello hello
字符串输出,字符串的变量名表示字符串的首地址,输出这个首地址,就可以输出整个字符串了(字符串在定义的时候默认最后一位是'/0'
字符)。
如果加上解引用符号*
的话,代表的是单个值,也就是字符串首地址对应的字面值。
五、char *s 和 char s[]的区别
char* s = "hello";
char s2[] = "hello";
先说他们的区别吧,s是指针,它的特性取决于它指向的对象,比如这里的话,它指向了一个字符串常量,所以s[n]是不可以修改的,但是s的指向是可以随便修改的(所以可以进行自增减等运算),下面的操作是正确的!
while(s!= '\0')
{
cout << *s << endl; // 注意s指向的是常量,所以不能改变它的值
s++;
}
s2的话,是一个分配了地址的字符数组,它里面的值只跟他自己定义有关,我们定义的是非常量字符数组的话,s2[n]的值是可以修改的。但是s2是分配了具体的地址,所以s2的指向是不能修改的(所以不能进行指针的一些计算)。
while(*s2 != '\0')
{
cout << *s2;
s2++; // 错误,这里的s2不是指针,它是一个实际的地址,这个地址值是不能修改的
}
for(int i = 0; i < sizeof(s2) - 1; i++)
{
cout << s2[i]; // 正确,并可以改变它的值
}
注意:
我们要知道,指针是一个对象,指针有自己的地址,它指向的类型的地址,是作为值保存在这个地址上,可以见下图:(用于解释上述内容,不具准确性)
对于第一种情况,指针并不是直接存储"hello"
这个字面值,而是保存它字面值存放的地址,由于"hello"是一个字面值,是一个常量值,是直接分配在常量池的,我们s只是指向它,由于这个值是常量值,所以它的值没法修改,而且定义的时候还会有警告,如果我们加上底层const
修饰,警告会消除。
六、int (*ptr)[10] 和 int* ptr[10]的区别
① 这里先讲下数组的一些只是,数组不允许拷贝和赋值(prime c++上是这样写的,但是我觉得应该是数组名不允许拷贝和赋值)
int a[] = {0,1,2,3};
int a2[] = a; // 错误,不允许拷贝
a2 = a; // 错误,不允许赋值
② 其次不存在引用的数组。
int& a[10] = xxx; //错误,引用不是对象,没法分配内存,所以这样的数组是不存在的。
③ &数组名:表示指向整个数组的指针。
数组名:表示数组第一个元素的地址。
下面就是正式的内容了:
int (*ptr)[10]:
这里的*
修饰的是ptr
,说明ptr
是个指针,它是一个指向有10个整型变量的数组的指针。
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int (*ptr)[10] = &a; // 这里只能使用&a,如果使用a就会出错,他会告诉你:不能将int *类型传递给int (*)[4]这种类型
int ptr[10];*
这里的ptr是一个数组的首地址,该数组有10个对象,每个对象的类型是int*
,就是保存了10个整型指针的数组。
int a = 10;
int* b = &a;
int* ptr[10] = {b,b,b,b,b,b,b,b,b,b}; // 这里面保存的全是 int* 指针
七、函数指针
函数指针指向函数而非对象,和其他指针一样,函数指针指向某种特定类型。函数的类型由它的返回值和形参共同决定,与函数名无关。
① bool* pf(const string&,const string&);
② bool (*pf)(const string&,const string&);
比较上面两种声明方式,第①种并不是函数指针,因为*
修饰的对象变成了返回值(优先级的原因),这里就变成了pf是一个函数了,返回值是bool*
类型。
第②种方式为正确的函数指针的声明,*
修饰的是pf,表示pf是一个指针,指向一个返回值为bool,参数为(const string&,const string&)的函数。
由于函数的类型是由返回值和形参决定的,所以函数指针代表的为一类函数,具体要使用哪个函数,我们还得给他赋值。
int testFunc(int a, char c, float f)
{
cout << a ;
cout << c;
cout << f << endl;
return a;
}
int (*pf)(int,char,float);
int main()
{
int a = 0;
char c = 'k';
float f = 1.0;
pf = testFunc; // 这里也可以写 pf = &testFunc;
int sum = pf(a,c,f);
cout << sum << endl;
}
结果截图:
八、函数指针形参
函数指针作为参数传递:
void func(bool pf(const string&,const string&,...))
void func(bool (*pf)(const string&,const string&,...))
int testFunc(int a, char c, float f)
{
cout << a ;
cout << c;
cout << f << endl;
return a;
}
void func(int a,char c,float f,int myFunc(int,char,float)) // 或者写成 func(int a,char c,float f,int (*myFunc)(int,char,float))
{
int sum = myFunc(a,c,myFunc);
cout << sum << endl;
}
int main()
{
int a = 0;
char c = 'k';
float f = 1.0;
func(a,c,f,testFunc);
}
结果截图:
还有一种方式,就是使用std::function
,需要包含头文件<functional>
,std::function
可以用来包装函数、函数指针和lambda表达式等可调用对象。
std::function<T<S...>>
表示返回类型为T,参数为S...
(可以有多个入参)的函数。
上面的方法可以这样写:
std::function<int(int,char,float)> callback;
int testFunc(int a, char c, float f)
{
cout << a;
cout << c;
cout << f << endl;
return a;
}
int main()
{
int a = 0;
char c = 'k';
float f = 1.0;
callback = testFunc;
int sum = callback(a,c,f);
cout << a << endl;
}
九、返回指向函数的指针
我们一般是无法返回函数的,只能通过返回函数指针的方法返回。所以我们要将返回类型写成指针形式,由于编译器不会自动将函数返回类型当作对应的指针类型处理。所以,想要声明一个返回函数指针的函数,最简单的方法是使用类型别名。下面这种写法是错的:
int (*)(int,char,float) retFunc() // 错误
{
return testFunc;
}
using F = int (int,char,float);
F为函数类型,并非为函数指针类型(为啥我就不说了,上面内容有)
using PF = (int*)(int,char,float)
PF为函数指针类型
像下面这样定义就是正确的:
PF retFunc() // 错误
{
return testFunc;
}
我们可以调用一下试一试:
int testFunc(int a, char c, float f)
{
cout << a;
cout << c;
cout << f << endl;
return a;
}
using PF = int(*)(int,char,float);
PF retFunc(){
return testFunc;
}
int main()
{
pf = retFunc();
int sum = pf(0,'k',1);
cout << sum << endl;
}
结果截图: