还在整理ing
重点博客记得看 https://blog.csdn.net/gaojing303504/article/details/80338787
https://blog.csdn.net/zhang_guyuan/article/details/60590075
常用的技术基础主要包括:编程语言相关;数据结构,操作系统,计算机网络,计算机组成原理;数据库相关;linux命令行相关;以及常用的工具等等。
编程语言常用有关于C++,python,golang,JAVA等,主要会根据几种语言的特性来问。
1. C++和别的语言相比有什么特点?
C++语言和C语言相比
2. 什么是指针?
我自己对指针的理解,指针相当于一个索引,不是通过变量的地址直接寻址,而是把变量地址存到指针ipoint中,通过ipoint进行索引在找到i的地址。
总结指针就是“变量内存地址下存放的是另一个变量的地址”的变量。 因此&i这种也是一种指针。
int i = 5;
int* ipoint = &i; //指针ipoint为指向i的指针,即ipoint变量地址下存储的是i的地址
cout << *ipoint << endl; //输出i的值
cout << ipoint << endl; //输出i的地址
cout << &ipoint << endl; //输出ipoint的地址
其中指针ipoint为指向i的指针,即ipoint变量地址下存储的是i的地址;
*ipoint 输出的是i的值;
ipoint 输出的是i的地址;
&ipoint 输出的是ipoint的地址。
下图1:整型变量i和指针变量ipoint的存储
2.1 符号 & 和 *
符号&就是取一个变量的地址,就是取一个内存地址下的变量内容。
但是的操作对象只能是指针(指针包括&i类型的指针)
int i = 5;
int* ipoint = *i; //==语法错误==,虽然int*和int*类型匹配,但是*的操作对象只能是指针;
cout << *(&i) <<endl; //输出i的值,先取i的地址再根据i的地址取值。
int* ipoint = *i; ===语法错误===,虽然int*和int*类型匹配,但是*的操作对象
只能是指针;
*(&i) 没有语法错误,先取i的地址再根据i的地址取值,输出的是i的值。
2.2 是复制还是指向问题
int* temp;
是声明一个指针变量temp,相当于只是对指针变量temp分配了空间,但是temp是一个空指针,因为没有对指向内容*temp分配空间。
- 复制问题:要求temp和*temp都要有内存空间。
以下代码是复制问题,直接更改指针指向内存下的内容,。
但是因为没有对 *temp分配空间,所以会编译报错。但是dev上面可以运行,因为dev会在复制时临时给一个随机地址存值,但是这种随机的空间没办法回收,会造成内存泄漏。
void swap2(int* a,int* b)
{
int* temp;
*temp = *a;
}
- 指向问题:只要求temp本身有空间,对*temp没有要求。
以下代码是指向问题,直接改的是指针指向的地址。
void swap3(int* a,int* b)
{
int* temp;
temp = a;
a = b;
b = temp;
}
2.3 字符指针char*
字符指针是一个稍微特殊一点点的指针,平常看起来会直接拿来当成字符串用,但是本质上还是指针。
- 赋值:可以直接用字符串的方式进行赋值,直接赋一个字符数组;
char* str = "hello";
- 输出:
- 从字符指针的本质看,str地址下存储的是字符数组的首地址即‘h’的地址,但是直接cout输出看,会直接打印出整个字符数组char[] 的内容:
char* str = "hello";
cout << str << endl; //输出结果:hello
cout << str[0] << endl; //输出结果:h
cout << *str << endl; //输出结果:h
cout << &str << endl; //输出结果:0031F99C
str 输出结果:hello;
str[0] 输出结果:h;
*str 输出结果:h;
&str 输出结果:0031F99C。
下图2:字符指针变量str和指向字符h以及连续字符的存储
- 但是为什么cout << str 会直接输出整个字符数组char[]的内容,而不只是指向的数组首地址?
原因: << 对字符串的重载,对str对整个字符数组char[]输出。详情见下:C++指针困惑,为什么char *p cout 直接输出了整个字符数组而不是输出首个地址 - 现在已知cout << str输出不是指向字符‘h’的地址,如果非要输出指针指向字符变量的地址:使用强制类型转换的方法把str转换成一般指针,输出时就不会再因为 << 对字符指针的重载输出hello了。
char* str = "hello";
cout << &str << endl; //输出为str变量的地址,即图2中的地址1
cout << (void*) str << endl; //类型强制转化,输出为指针指向变量的地址,即地址2
&str 输出为str变量的地址,即图2中的地址1;
(void*) str 是类型强制转化,输出为指针指向变量的地址,即地址2。
- 由char* str = "hello"赋值导致的**程序崩溃**问题:
char* str = "hello";
strcpy(str, "olleh");
cout << str << endl;
- 以上代码平平无奇,但是却会导致程序崩溃。原因是因为由 = 对char* 变量赋值时,系统在常量区给“hello”开了空间,指针str指向字符‘hello’,即str指向的是常量。即当前指向地址下的内容不可以更改,但是可以直接更改指针指向的地址本身。
- 修改指向地址下的内容:导致崩溃
char* str = "abcd"; // ==程序崩溃==
str[0] = 'p';
char* str = "abcd"; 会导致 ===程序崩溃===
- 修改指针指向的地址本身:指向一个新的对象变量
char* str = "abcd";
str = "dbca"; //相当于直接改了指针变量下存储的变量地址,将“dbca”换成十六进制
cout << str << endl;
char* str = "abcd" 先给str分配指向对象,
str = "dbca" 更改指针变量下存储的变量地址,将“dbca”换成十六进制相当于存的地址。
- 需要修改指向对象的值:不用char*而用char[]
char str[6] = "hello";
strcpy(str, "olleh");
cout << str << endl; //一段正常的程序,输出为olleh
2.4 指向数组的指针
- 指向一维数组:两种方式
- 直接a 进行赋值,a本身就代表数组a的首地址;
int a[10];
int* apoint;
apoint = a;
- 表达的更清楚一点,取首元素a[0]的地址赋值;
int a[10];
int* apoint;
apoint = &(a[0]);
- 指向二维数组:*apoint才表示指向也是指向二维数组的指针,apoint表示指向(*apoint)的指针。
- 定义方法1:不用先声明二维数组,直接声明指针
int (*apoint)[3][6];
cout << sizeof(*apoint) << endl;
- 定义方法2:先声明一个二维数组,然后声明指向该数组的指针
int a[3][6];
int** apoint = NULL;
*apoint = &(a[0][0]); //*apoint已经表示指向二维数组的指针,存储的是二维数组首元素的地址;
(*apoint)表示apoint是指向二维数组int [3][6]数组的指针,并不是取内容,大小为72字节;
(**apoint)表示指向一维数组int [6]的指针,大小为sizeof(int) * 6 = 32字节;
(***apoint)表示指向int的指针,大小为sizeof(int) = 4字节;
- 指针初始化1:和一维数组 apoint = a 初始化不一样,取apoint = &a
int a[3][6];
int (*apoint)[3][6];
(apoint) = &a;
- 指针初始化2:取*apoint = &(a[0][0])
int a[3][6];
int (*apoint)[3][6];
(*apoint) = &a[0][0];
- 指针初始化3:取(*apoint) = a这样会有编译错误
- 一个复杂的例子:
double* (*a)[3][6];
cout << sizeof(a) << endl; //a只是指针,指向指针(double*) [3][6],即a内存地址下存的是地址,大小为4字节;
cout << sizeof(*a) << endl; //*a就是(double*)[3][6],大小为sizeof(double*) * 3 * 6 = 72字节;
cout << sizeof(**a) << endl; //**a就是(double*)[6],大小为sizeof(double*) * 6 = 24字节;
cout << sizeof(***a) << endl; //***a就是(double*),大小为sizeof(double*) = 4字节;
cout << sizeof(****a) << endl; //****a就是double,大小为sizeof(double) = 8字节;
sizeof(a) 大小为4字节,因为a是指针,内存下存的就是一个地址;
sizeof(*a) = sizeof(double*) * 3 * 6 = 72字节,因为*a内存下就是double(*)[3][6];
sizeof(**a) = sizeof(double*) * 6 = 24字节,因为**a内存下就是(double*)[6];
sizeof(***a) = sizeof(double*) = 4字节,因为***a内存下就是(double*);
sizeof(****a) = sizeof(double) = 8字节,因为****a内存下就是一个double;
3. 指针*和引用&的区别?
3.1 NULL问题:指针可以为空指针,但是引用不可以为空引用
- 从定义上讲:引用相当于一个变量的别名,定义时一定要有初始化,并不能声明一个不指向任何对象的引用。
- 指针可以为空,它是值指向某个变量地址的变量,空指针即不指向任何对象。
3.2 改变指向对象问题:指针可以改变指向的对象,引用不可以
- “至死不渝”的引用: 不可以改变指向的对象,不能从变量a的别名改成变量b的别名;
int i = 13;
int j = 1313;
int& iref = i;
iref = j;
cout << i << " " << iref << endl; //输出结果为:1313 1313
以上代码编译没有问题,但是并没有改变iref的指向,iref = j相当于直接使用引
用的存储地址进行赋值,由于引用是共享地址,所以相当于直接对i进行了赋值。
- “花心大萝卜”的指针:可以改变指向的对象,只要改变内存下存储的地址就指向新的对象,和之前指向的对象不再有关联。
int i = 13;
int j = 1313;
int* ipoint = &i;
ipoint = &j;
cout << i << " " << *ipoint << endl; //输出结果为:13 1313
以上代码ipoint本来指向对象变量i,后来改了指向对象变量b。
3.3 内存问题:指针变量有自己的空间,而引用和指向变量共享空间
- 引用只是别名,和指向变量本体共享空间;
- 指针有自己的空间,和指向的对象类型一致,但是本质上没有直接的关联;
3.4 使用时引用比指针更安全
- 指针可以随意切换指向对象;
- 指针可以不被初始化;
- 指针使用时要检验是否为NULL;
- const指针虽然不能改变指向,但是仍然可能有NULL问题;
- 可能有野指针的问题:空指针是指指针目前为空闲,没有指向任何对象;而野指针是指一个指针指向了一块不可使用的内存空间,产生原因主要有三个:
- 指针没有进行初始化:任何指针变量初始化时不会自动设置为NULL指针,它的设置是随机的,可能指向一块不可使用的内存空间,变成野指针;
- delete或者free的时候,只是释放了指针指向的内存空间释放掉,如果没有将指针置为NULL,该指针变成野指针。另外如果有多个指针指向同一块内存区域,当一个指针delete或者free,其他指针都将变成野指针;
- 当指针操作超出了指向内存空间的作用范围,此时指针越界也会变成一个野指针;
4. 类的问题
4.1 public, protected, private
- 属性:
private: 只能由该类中的函数访问、其友元函数访问,不能被任何其他访问,该类的对象也不能访问;
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问;
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问;
注:友元函数包括两种:设为友元的全局函数,设为友元类中的成员函数 - 继承方式:
public继承:不改变基类成员的访问权限;
private继承:使得基类所有成员在子类中的访问权限变为private
protected继承:将基类中public成员变为子类的protected成员,其它成员的访问 权限不变。
4.2 浅拷贝和深拷贝
简单说【浅拷贝】是增加了一个指针,指向原来已经存在的内存。而【深拷贝】新开辟了一块空间。
- 浅拷贝:
只是增加了一个指针,指向原来存在对象的内存,共享内存。缺点为:
当一个对象值有改变,另一个对象的值随之改变;
当其中一个对象释放了内存,另一个对象指针将变成野指针;
当类中的两个对象指向同一个内存空间,一个对象的析构函数释放空间后,另一个对象再次执行析构函数会出现错误; - 深拷贝:
开辟了一块新的内存地址用于存放复制的对象,只拷贝内容。
4.3 类中浅拷贝和深拷贝的实现—拷贝构造函数
当没有自定义拷贝构造函数时,编译器会自动写一个拷贝构造函数,实现方式为浅拷贝。
class String
{
private:
char* pstr;
public:
#if !is_deep_copy
//浅拷贝方式实现的拷贝构造函数,不会为新对象中的属性分配空间,
//只是把对象内的属性指向同一块内存。
String(const String& s):pstr(s.pstr)
{}
#endif
#if is_deep_copy
//深拷贝方式实现的拷贝构造函数,先重新分配空间,再复制内容。
String(String& s):pstr(new char[strlen(s.pstr)+1])
{
strcpy(pstr,s.pstr);
}
#endif
}
4.4 拷贝构造函数和=运算符重载问题
class String
{
private:
char* pstr;
public:
String(const char *pStr = " ")
{
if(pStr == NULL)
{
pstr = new char[1];
*pstr = '\0';
}
else{
pstr = new char[strlen(pStr) + 1];
strcpy(pstr, pStr);
}
}
#if !is_deep_copy
String(const String& s):pstr(s.pstr)
{}
String& operator=(const String& s)
{
if(this != &s)
{
delete[] pstr;
strcpy(pstr,s.pstr);
}
return *this;
}
#endif
#if is_deep_copy
String(String& s):pstr(new char[strlen(s.pstr)+1])
{
strcpy(pstr,s.pstr);
}
String& operator=(const String& s)
{
if(this != &s)
{
char* temp = new char[strlen(s.pstr) + 1];
delete[] pstr;
strcpy(temp,s.pstr);
pstr = temp;
}
return *this;
}
#endif
~String()
{
if(pstr != NULL)
{
delete[] pstr;
pstr = NULL;
}
}
};
int main()
{
String s1("hello"); //调用构造函数
String s2 = s1; //调用拷贝构造函数
String s3("nice to meet you"); //调用构造函数
s3 = s2; //调用=运算符重载函数
return 0;
}
其中String s1("hello"); 调用构造函数
String s2 = s1; 看起来是调用了运算符=,但其实是调用拷贝构造函数。
s3 = s2; 调用=运算符重载函数
对象进行赋值时,调用=运算符重载函数或者是拷贝构造函数?
主要看 是否创造了新对象,产生新对象即为拷贝构造函数,未产生新对象即为=运算符重载函数,
对于s2是在用s1赋值时产生的新对象,因此调用的是拷贝构造函数;
对于s3是已经创建好的对象,再利用s2对s3进行赋值时并未产生新对象,因此此处调用的=运算符重载函数。
5. sizeof()的问题?
5.1 sizeof是关键字而不是函数
首先得知道sizeof是c语言中的一个关键字而不是函数,在编译阶段就已经确定。
int a = 1;
int b = sizeof(++a);
cout << a << endl;
由于sizeof不是一个函数,关键字在编译阶段就已经确定,所以括号中不会被执行,
因此输出为结果:1。
5.2 一般变量的sizeof大小
变量类型 | sizeof() |
---|---|
char | 1字节 |
short | 2字节 |
int | 4字节 |
unsigned int | 4字节 |
unsigned = unsigned int | 4字节 |
float | 4字节 |
double | 8字节 |
__int64 | 8字节 |
5.3 class和struct的sizeof
- 空class和struct:编译器仍留1字节的空间
struct A{};
class B{};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
}
空的结构体A和类B,sizeof(A)和sizeof(B)都为1字节。
- 非空class和struct:编译器不会多给1字节
struct A{ int a; };
class B{};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
}
对于空的结构体和类,仍然会分配一个1字节的空间,但是对于非空的类和结构体,
不会单独多加1字节的空间,因此sizeof(A)和sizeof(B)分别为4字节和1字节;
- class和struct整体空间一定是占用空间最大的元素的整数倍,否则要有数据对齐
对于class和struct,对于一般的变量元素,直接以元素的sizeof进行补齐;
但是对于数组类型的元素,不是看成一个整体,而是以元素为单位进行补齐。
class s1
{
char a[8];
};
struct s2
{
double d;
};
class s3
{
s1 s;
char ch;
};
struct s4
{
s2 s;
char ch;
};
int main()
{
cout <<sizeof(s1) << endl;
cout <<sizeof(s2) << endl;
cout <<sizeof(s3) << endl;
cout <<sizeof(s4) << endl;
}
sizeof(s1)输出结果为8字节,sizeof(s2)输出结果为8字节;
sizeof(s3)输出结果为9字节,sizeof(s4)输出结果为16字节;
对于s4来说,s2大小为8字节,需要进行对齐,因此两个元素对齐8字节为16字节;
但是对于s3来说,s1虽然为8字节,但是对于数组是每个元素单独存放,以元素大小
为单位进行对齐,因此s3中相当于9个char元素,不需要特别补齐。
- class中static成员变量不算入对象内存空间,const算入
class B
{
int a;
const int b;
static int c;
};
int main()
{
cout << sizeof(B) << endl;
}
sizeof(B)大小为8字节,其中类中的static成员变量是属于类域而不是属于对象的,
因此static的成员变量不算入class B的内存大小;但是const成员变量算入class B的
内存大小;
- class一般成员函数不算入对象内存空间,如果有virtual成员函数,对象中会包含一个指向虚函数表的指针
class B
{
int fun(int b){ return b; };
virtual void func(){ int x = 0; };
};
int main()
{
cout << sizeof(B) << endl;
}
sizeof(B)大小为4字节,其中一般的函数fun()没有计入对象的空间大小中,但是对于
virtual函数func(),对象包含一个指针指向虚函数表,因此指针大小为4字节。
5.4 与strlen()函数的比较
- strlen()计算数组的长度,长度不包含结束符
- strlen()是用来计算数组长度的函数,用’\0’ 作为数组结束符作为判断;
- 统计单位为个,即 统计数组中有多少个元素;
- 最后统计的长度 不会包含’\0’结束符;
- sizeof()统计数据所用内存空间,包含结束符占用空间
- sizeof()是用来统计数据所占内存空间的大小;
- 用字节数表示占用内存空间;
- 最后统计的空间 会包含结束符’\0’在内;
5.5 指针和静态数组的sizeof()问题
- 指针内存大小是指向对象地址大小,32位系统就是4字节,64位就是8字节
对于指针来说,不管指向的对象是一般变量或者类或者结构体,不管指向对象的内存大小,都是指对象的地址。因此 32位系统的指针大小全部为4字节,64系统的指针大小全部为8字节。
- 静态数组作为形参使用时,数组名称当做指针使用
void f(int p[])
{
cout << sizeof(p) << endl;
}
int main()
{
int p[5];
f(p);
}
当系统为32位时f(p)输出的结果为4字节,64系统时输出的结果为8字节,因为数组p
作为函数形参使用时,被当做一个指针指向原数组,因此输出p的大小为指针大小。
- 对于字符数组,sizeof()多计算末尾结束符’\0’的大小
char ch[] = "hello";
cout << sizeof(ch) << endl;
sizeof(ch)输出结果为6字节,因为hello5个字符加一个'\0'字节,用6字节。
- 指向二维数组的指针的复杂问题
double* (*a)[3][6];
cout << sizeof(a) << endl; //a只是指针,指向指针(double*) [3][6],即a内存地址下存的是地址,大小为4字节;
cout << sizeof(*a) << endl; //*a就是(double*)[3][6],大小为sizeof(double*) * 3 * 6 = 72字节;
cout << sizeof(**a) << endl; //**a就是(double*)[6],大小为sizeof(double*) * 6 = 24字节;
cout << sizeof(***a) << endl; //***a就是(double*),大小为sizeof(double*) = 4字节;
cout << sizeof(****a) << endl; //****a就是double,大小为sizeof(double) = 8字节;
sizeof(a) 大小为4字节,因为a是指针,内存下存的就是一个地址;
sizeof(*a) = sizeof(double*) * 3 * 6 = 72字节,因为*a内存下就是double(*)[3][6];
sizeof(**a) = sizeof(double*) * 6 = 24字节,因为**a内存下就是(double*)[6];
sizeof(***a) = sizeof(double*) = 4字节,因为***a内存下就是(double*);
sizeof(****a) = sizeof(double) = 8字节,因为****a内存下就是一个double;