(记录学习中遇到的问题,仅做笔记)
1、纯虚函数的正确声明:
virtual void print()=0
2、常对象
#include<iostream>
using namespace std;
class Sample{
public:
Sample(int i,int j){
x=i;
y=j;
}
void disp(){
cout<<"disp1"<<endl;
}
void disp()const{
cout<<"disp2"<<endl;
}
private:
int x,y;
};
int main(){
const Sample a(1,2);
a.disp();
return 0;
}
输出:disp2
说明:如果一个对象说明为常对象,则通过该对象只能调用它的常成员函数。题中,对象a被定义成类Sample的常对象,通过对象a只能调用其常成员函数disp,所以程序最后输出disp2。
3、空类
#include <stdio.h>
#include <stdlib.h>
class A
{
};
int main()
{
printf("%d\n",sizeof(class A));
return 0;
}
输出为:1
实际上,这是类结构体实例化的原因,空的类或结构体同样可以被实例化,如果定义对空的类或者结构体取sizeof()的值为0,那么该空的类或结构体实例化出很多实例时,在内存地址上就不能区分该类实例化出的实例,所以,为了实现每个实例在内存中都有一个独一无二的地址,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以空类所占的内存大小是1个字节。
4、结构体长度
若有下面的说明和定义:
struct test
{ int ml;
char m2; float m3;
union uu
{char ul[5]; int u2[2];} ua;
} myaa;
则sizeof(struct test )的值是20
union外:int为4,char为1,float为4。由于char为1,后面的3字节装不下float,因此对齐后char占4字节(空3字节)。注:如果char后面定义short,那么char占1,short占2。对齐后char就占2。union中取最大的,5char(1)<2int(8)。所以union为8。int(4)+char(4)+float(4)+union(8)=20。
存储原则:有两个原则:(对应下面例子)
struct A
{
char a[5];
int b;
short int c;
}structA;
1)各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数
即当 A中的a占用了5个字节后,b需要占用四个字节,此时如果b直接放在a后,则b的起始地址是5,不是sizeof(int)的整数倍,所以
需要在a后面补充3个空字节,使得b的起始地址为8. 当放完b后,总空间为5+3+4 = 12. 接着放c,此时为 12 + 2 = 14.
2)为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
这是说A中占用最大空间的类型,就是int型了,占用了4个字节,那么规定A占用的空间必须是4的整数倍。本来计算出来占用的空间为14,
不是4的整数倍,因此需要在最后补充2个字节。最终导致A占用的空间为16个字节。
参考:https://blog.csdn.net/nisxiya/article/details/22456283
补充:结构体内存对齐规则(请记住三条内存规则(在没有#pragam pack宏的情况下)
结构体所占用的内存与其成员在结构体中的声明顺序有关,其成员的内存对齐规则如下:
(1)每个成员分别按自己的对齐字节数和PPB(指定的对齐字节数,32位机默认为4)两个字节数最小的那个对齐,这样可以最小化长度。如在32bit的机器上,int的大小为4,因此int存储的位置都是4的整数倍的位置开始存储。
(2)复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,结构体数组的时候,可以最小化长度。
(3)结构体对齐后的长度必须是成员中最大的对齐参数(PPB)的整数倍,这样在处理数组时可以保证每一项都边界对齐。
(4)结构体作为数据成员的对齐规则:在一个struct中包含另一个struct,内部struct应该以它的最大数据成员大小的整数倍开始存储。如 struct B 中包含 struct A, struct A 中包含数据成员 char, int, double,则 struct A 应该以sizeof(double)=8的整数倍为起始地址。
实例:
struct A
{
char a; //内存位置: [0]
double b; // 内存位置: [8]...[15]
int c; // 内存位置: [16]...[19] ---- 规则1
}; // 内存大小:sizeof(A) = (1+7) + 8 + (4+4) = 24, 补齐[20]...[23] ---- 规则3
struct B
{
int a, // 内存位置: [0]...[3]
A b, // 内存位置: [8]...[31] ---- 规则2
char c, // 内存位置: [32]
}; // 内存大小:sizeof(B) = (4+4) + 24 + (1+7) = 40, 补齐[33]...[39]
*注释:(1+7)表示该数据成员大小为1,补齐7位;(4+4)同理。*
5、继承多态
using namespace std;
class A{
public:
virtual void f() { cout << "A::f() "; }
void f() const { cout << "A::f() const "; }
};
class B : public A {
public:
void f() { cout << "B::f() "; }
void f() const { cout << "B::f() const "; }
};
void g(const A* a) {
a->f();
}
int main(int argc, char *argv[]) {
A* p = new B();
p->f();
g(p);
delete(p);
return 0;
}
输出:
B::f() //因为A的f()是虚函数
A::f() const //因为A的void f() const不是虚函数
6、继承:子类父类的构造析构顺序
子类对象生成时:先调用父类的构造函数,然后在调用子类的构造函数; 析构时相反
class CBase
{
public:
CBase(){cout<<”constructing CBase class”<<endl;}
~CBase(){cout<<”destructing CBase class”<<endl;}
};
class CSub : public CBase
{
public:
CSub(){cout<<”constructing CSub class”<<endl;}
~CSub(){cout<<”destructing CSub class”<<endl;}
};
void main()
{
CSub obj;
}
输出:
constructing CBase class
constructing CSub class
destructing CSub class
destructing CBase class
7、常成员函数
void print() const;
8、typedef
typedef的用途: 为已有的数据类型重新命名。
//1. 基本类型
int a=10;
//为整型重新命名
typedef int ZS;
ZS b=10;
//2. 结构体的使用
a. 为结构体重新命名:
//ST等价于struct Student
typedef struct Student
{
int id;
char sex;
}ST;
b. 为结构体重新命名(指针类型)
//PST等价于struct Student*
typedef struct Student
{
int id;
char sex;
}* PST;
c. 混合使用
//PST等价于struct Student*
//ST等价于struct Student
typedef struct Student
{
int id;
char sex;
}* PST,ST;
typedef和#define的区别
typedef是一种类型别名,而#define只是宏定义。二者并不总是可以互换的。
如下例所示:
typedef char *pStr1;
#define pStr2 char *;
pStr1 s1, s2;
pStr2 s3, s4;
其中s1, s2, s3是char*类型,而s4是char类型。
typedef需要注意的事项
typedef char* pStr;
const char* p1 = "hello";
const pStr p2 = "hello";
p1++;//正常
p2++;//报错
p1和p2都是常量指针,意思是指针指向的内容不能修改,而指针是可以修改的。
对于p1++,因为常量指针是可变的。
而p2是我们定义的别名,而不是系统固有类型,编译器在编译时,会认为p2是常量,不可修改,
所以p2++会报错。
参考链接:
https://blog.csdn.net/wue1206/article/details/81612883
https://www.cnblogs.com/shijingjing07/p/5591896.html
9、作用域问题
intx=5,y=7;
void swap ()
{ int z;
z=x;
x=y;
y=z
}
int main (void)
{
int x=3,y=8;
swap();
printf ("%d,%d\n",x,y) ;
}
输出结果为3,8
解析:
//------全局作用域开始
int x = 5, y = 7;
//------作用域1开始
void swap ( )
{ int z ;
z = x ; x = y ; y = z ;
}
//------作用域1结束
//------作用域2开始
main( )
{ int x = 3, y = 8; //----注1
swap ( ) ;
printf ( " %d , %d \n ", x , y ) ;
}
//------作用域2结束
//------全局作用域结束
当作用域2里的printf函数打印x,y的时候,它会先找自己作用域是否有定义x,y,有的话就直接用了,如果没有定义,才会去外一层的作用域找已经定义的x,y。
swap()函数调用后的效果是把全局作用域下定义的x,y的值改变了,因为swap()函数体中(也就是作用域1中),没有定义x,y(main函数中的x,y并没有作为参数传进去),所以他会去找上层作用域(全局),从而进行赋值。
而printf的时候,main()里(作用域2)已经定义了x,y,所以全局变量里定义的x,y在作用域2里是不可视的,且值也没有改变,所以还是x=3,y=8。
10、指针数组
#include <stdio.h>
void fun( char ** p)
{
int i;
for(i=0;i<4;i + + )printf("% s",p[i]);
}
main( )
{
char *s[6]={ "ABCD", "EFGH", "IJKL", "MNOP", "QRST", "UVWX" };
fun(s);
printf("\n");
}
输出:ABCDEFGHIJKLMNOP