一、explicit关键字
作用是表明该构造函数是显示的, 而非隐式的.不能进行隐式转换! 类构造函数默认情况下即声明为implicit(隐式).注意没有implicit这个关键字。
// demo 16-1.c
#include <iostream>
#include <string>
using namespace std;
class student {
public:
student(int _age)
{
age = _age;
cout << "age=" << age << endl;
}
student(int _age, const string _name)
{
age = _age;
name = _name;
cout << "age=" << age << "; name=" << name << endl;
}
~student()
{
}
int getAge()
{
return age;
}
string getName() {
return name;
}
private:
int age;
string name;
};
int main(void) {
student xiaoM(18); //显示构造
student xiaoW = 18; //隐式构造
//student xiaoHua(19, "小花"); //显示构造
//student xiaoMei = { 18, "小美" }; //隐式构造 初始化参数列表,C++11 前编译不能通过,C++11新增特性
system("pause");
return 0;
}
重点:
student xiaoM(18); //显示构造
student xiaoW = 18; //隐式构造
隐式构造 初始化参数列表,C++11 前编译不能通过,C++11新增特性
隐式构造可能会造成混淆,如像是普通赋值操作,所以想不支持隐式构造,那么就加上explicit。
二、左值与右值
按字面意思,通俗地说。以赋值符号 = 为界,= 左边的就是左值(lvalue),= 右边就是右值(rvalue)。
int a = 666;
左值 右值
int b = 888;
左值 右值
int c = a + b;
左值 右值 右值
lvalue - 代表一个在内存中占有确定位置的对象(变量)(换句话说就是有一个地址)。
rvalue - 通过排他性来定义,每个表达式不是lvalue就是rvalue。因此从上面的lvalue的定义,rvalue是在不在内存中占有确定位置的表达式,而是存在在寄存器中。常量都是右值。
一般情况下,变量在左就是左值,在右就是右值。
所有的左值(无论是数组,函数或不完全类型)都可以转换成右值。
在linux中查看汇编代码:
#include<stdio.h>
int main()
{
int a=1;
int b=2;
int c=3;
c=a+b;
printf("%d\n",c);
printf("%p %p\n",&a,&b);
return 0;
}
gcc test.c -S
movl $1, -12(%rbp)
movl $2, -8(%rbp)
movl $3, -4(%rbp)
三个变量存于栈rbp中
movl -12(%rbp), %edx
movl -8(%rbp), %eax
a、b的值移动到寄存器中
addl %edx, %eax
movl %eax, -4(%rbp)
将a和b相加的值放入eax寄存器中,将结果移动到c的位置
总结:为什么不能a+b=c?就是因为a+b是在寄存器中,在内存中没有一个确定的地址,所以不能作为左值。
特殊情况,静态变量返回引用可以作为左值。
三、函数返回值当引用
C++引用使用时的难点:
-
当函数返回值为引用时
若返回栈变量,不能成为其它引用的初始值,不能作为左值使用 -
若返回静态变量或全局变量
可以成为其他引用的初始值
即可作为右值使用,也可作为左值使用 -
返回形参当引用
(注:C++链式编程中,经常用到引用,运算符重载)
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
int demo1() {
int i = 0;
//printf("i 的地址: %p, i=%d\n", &i, i);
return i;
}
int &demo(int **addr) {
int i = 666;
*addr = &i;
printf("i 的地址: %p, i=%d\n", &i, i);
return i;
}
int &demo_static(int **addr) {
static int i = 666;
*addr = &i;
printf("demo_static: i 的地址: %p, i=%d\n", &i, i);
return i;
}
//3. 函数返回形参(普通参数)当引用,危险
int &demo3(int var) {
var = 666;
return var;
}
//4. 函数返回形参(引用)当引用,这里本身传入是引用,返回也是引用,相当于是同一个,不是局部变量,所以没事
int &demo4(int &var) {
var = 666;
return var;
}
int main(void) {
int *addr = NULL;
int ret = demo(&addr); //这种情况没有问题,因为没有用引用去接,接到的就是一个值
//第一种情况 函数返回局部变量引用不能成为其它引用的初始值
//int &i1 = demo(&addr);
//i1 = 888;
//printf("addr: %p i1=%d\n", addr, i1);
//demo(&addr);
//demo1();
//printf("addr: %p i1=%d\n", addr, i1);
//第二种情况 函数返回局部变量不能做为左值
/*demo(&addr) = 888;
printf("1. addr: %p value: %d\n", addr, *addr);
demo1();
printf("2. addr: %p value: %d\n", addr, *addr);
*/
//第三种情况 返回静态变量或全局变量可以成为左值或是其它引用的初始值
//demo_static(&addr) = 888;
int &i1 = demo_static(&addr);
i1 = 888;
printf("1. addr: %p value: %d\n", addr, *addr);
demo1();
printf("2. addr: %p value: %d\n", addr, *addr);
demo_static(&addr);
system("pause");
return 0;
}
总结上例:
第1、2种情况,都是改变的局部变量引用,那么当修改这个引用的值时,还能改变,但是因为局部变量一旦出了括号,生命周期就结束了,所以如果此时用另外一个函数可能分配的栈是同一个,那么此时这个函数就可能改变这个栈中的值,说明这个局部变量是不可靠的,随时都有可能被别人改变。
函数内部变量生存周期很短,生命周期就在函数内部,返回引用时不能用引用接收,因为有可能已经被释放掉了。但是本身是引用,或者返回的是静态变量/全局变量就没事,地址一直在用,不会被别人修改。
四、结构体内存对齐
#include <stdio.h>
#include <stdlib.h>
using namespace std;
struct A{
char c;
int i;
};
struct B{
char c;
int i;
double d;
};
struct C{
char c;
int i;
double d;
char c1;
};
int main(){
printf("sizeof(A): %d\n", sizeof(struct A)); //8
printf("sizeof(B): %d\n", sizeof(struct B)); //16
printf("sizeof(C): %d\n", sizeof(struct C)); //24
system("pause");
return 0;
}
以上输出的结果并非实际成员占用的字节数,这就是结构体的内存对齐!
结构体内存对齐原因
1.平台原因(移植原因):
“不是所有的硬件平台都能访问任意地址上的任意数据;某些硬件平台只能在某些特定地址处取某些特定的数据,否则就会抛出硬件异常”。也就是说在计算机在内存读取数据时,只能在规定的地址处读数据,而不是内存中任意地址都是可以读取的。
2.效率原因:
正是由于只能在特定的地址处读取数据,所以在访问一些数据时,对于访问未对齐的内存,处理器需要进行两次访问;而对于对齐的内存,只需要访问一次就可以。 其实这是一种以空间换时间的做法,但这种做法是值得的。
结构体对齐规则
1.第一个成员在结构体变量偏移量为0 的地址处,也就是第一个成员必须从头开始。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数为编译器默认的一个 对齐数与该成员大小中的较小值。vs中默认值是8,Linux默认值为4(可以通过#pragma pack (N) 修改,使用#pragma pack(show) 可以查看对齐值,但修改时N的取 值只能设置成1, 2,4,8,16.
3.结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
4.如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。