8种机械键盘轴体对比
本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?
1.指针数组和数组指针(读法与顺序相反)
int (*p) [n]; 数组指针,首先是一个指针然后这个指针指向整型数组。
int *p[n]; []优先级更高,首先是一个数组,int* 说明了这个数组的元素是int指针。2.常量指针和指针常量(读法与顺序相同)
int const* p; 常量指针,首先是一个指针这个指针指向常量。
int * const p; 指针常量,首先是一个常量,并且用指针修饰,指针不能更改。3.字符串转换为整数
输入的指针不能为空,对正负值进行判断,对输入的字符进行判断是否包含非数值字符,以及溢出处理。除了完成基本的功能外,还会关注是否考虑了边界条件、特殊输入(Null指针,空字符串等)以及错误处理。4.sizeof(Class)
定义空类型,里面没有任何成员变量和成员函数,对该类似求sizeof(),得到的结果是1。空类型的实例不包含任何信息,本来应该是0,但是当声明该类型的实例时,必须在内存中占有一定的空间,否则无法使用。具体由编译器决定。加上构造和析构函数也是一样,只需要知道函数的地址即可。函数的地址只与类型有关而与实例无关。如果把析构函数标记为虚函数,会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针,32位上4个字节,64位上8个字节。5.拷贝构造函数
拷贝构造函数不允许传值,如果允许就会在拷贝构造函数内调用拷贝构造函数,就会形成永无休止的递归调用从而导致栈溢出。所以是不允许的,标准形式为A(const A& other)。6.赋值运算符函数1
2
3
4
5
6
7
8
9
10
11CMyString& operator=(const CMyString &str)
{
if(this == str)
{
return *this;
}
delete []m_pData;
m_pData = NULL;
m_pData = new char[strlen(str.m_pData) + 1];
strcpy(m_pData, str.m_pData);
}注意点:是否把返回值类型声明为该类型的引用,并在函数结束前返回实例自身的引用(*this)。只有返回引用才允许连续赋值。否则如果函数的返回值是void,str1=str2=str3无法通过编译。
是否把传入的参数类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次拷贝构造函数。声明为引用可以避免消耗,提升效率。在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的引用参数加上const关键字。
是否释放实例自身已有的内存,忘记在新内存前释放自己已有的空间程序将出现内存泄露。
是否判断传入参数和当前实例是否相同,如果相同则直接返回,如果事先不判断就进行赋值,那么在释放实例自身的内存时会出现严重的问题;当*this和传入参数相同时,一旦释放了自身内存,传入参数的内存同时被释放,再也找不到需要赋值的内容了。高级做法:
再分配内存前先用delete释放了实例m_pData的内存。如果此时内存不足导致new char抛出异常,m_pData将是一个空指针,这样会引起程序崩溃。违背了异常安全性的原则。在赋值运算符函数中实现异常安全性,有两种方法。一是先用new分配新内容在用delete进行释放已有内容。这样只在分配内容成功后再释放原来的内容,当分配内存失败时我们可以确保实例不会被修改。更好的方法是先创建临时实例,再交换临时实例和原来的实例。1
2
3
4
5
6
7
8
9
10
11CMyString& operator=(const CMyString &str)
{
if(this != &str)
{
CMyString strTemp(str);
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
return *this;
}
strTemp是一个局部变量,但程序运行到if外面时出了变量的作用域,就会自动调用strTemp的析构函数,把strTemp.m_pData所指向的区域释放掉。相当于自动调用析构函数释放实例的内存。在CMyString的构造函数里用new分配内存。如果由于内存不足抛出bad_alloc等异常,我们还没有修改原来的实例状态,所以保证了异常安全性。7.vector、数组、指针
为了解决数组空间效率不高的问题,人们又设计实现了多种动态数组,比如c++的stl中的vector。为了避免浪费,我们先为数组开辟较小的空间,然后往数组中添加数据。当数组的数目超过数组的容量时,我们再重新分配一块更大的空间(stl中的vector每次扩充容量时,新的容量都是前一次的两倍),把之前的数据复制到新的数组中,再把之前的内存释放,这样就能减少内存的浪费。每次扩充数组的容量都有大量的额外操作,这对时间性能有负面影响,因此使用动态数组要尽量减少改变数组容量大小的操作。在c++中当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。8.字符串
sizeof(string),string的实现在不同的库中可能有不同,但是同一个库中相同点是,无论string中放多长的字符串,它的sizeof都是固定的,字符串的所占空间是从堆中动态分配的,与sizeof无关。自己的机器上是32。c/c++中每个字符串都以字符’ ’作为结尾,这样可以方便的找到字符串的最后尾部。但由于这个特点,每个字符串中都有一个额外字符开销,可能会造成越界。1
2char str[10];
strcpy(str, "0123456789");
由于 的原因,要正确的复制该字符串,至少需要一个长度为11个字节的数组。
为了节省内存,C/C++把常量字符串放到单独的一个内存区域。当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址。但使用常量内存初始化数组,情况会有所不同。1
2
3
4
5
6char str1[] = "hello world";
char str2[] = "hello world";
char* str3 = "hello world";
char* str4 = "hello world";
string str5 = "hello world";
string str6 = "hello world";
str1和str2两个字符串数组,会为它们分配两个长度为12个字节的空间,并把“hello world”分别复制进去。这是两个初始地址不同的数组。str3和str4是两个指针,我们无须为它们分配内存来存储字符串中的内容,而只需要把它们指向”hello world”在内存中的地址即可。常量字符串在内存中只有一个拷贝,因此str3和str4指向同一个地址。str5与str6也是相同的,并且与str3公用内存地址。
合并两个数组(包括字符串)时,如果从前往后复制每个数字(或字符)需要重复移动数字(或字符)多次,那么可以考虑从后向前复制,这样就能减少移动的次数,从而提高效率。(替换空格题目)9.链表
特别注意向链表添加节点函数的第一个参数是指向指针的指针。当我们向空链表插入一个节点时,新插入的节点就是链表的头指针。由于此时会改动头指针,因此必须把pHead参数设为指针的指针。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void AddToTail(ListNode** pHead, int value)
{
ListNode* pNew = new ListNode();
pNew->m_nValue = value;
pNew->m_pNext = NULL;
if(*pHead == NULL)
*pHead = pNew;
else
{
ListNode* temp = *pHead;
while(temp->m_pnext != NULL)
{
temp = temp->m_pnext;
}
temp->m_pnext = pNew;
}
}
10.红黑树
红黑树是把树中的节点定义为红黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。在c++的stl中,set、multiset、map、multimap等数据结构都是基于红黑树实现的。11.c++成员变量的初始化
c++中成员变量的初始化顺序只与它们在类中声明的顺序有关,而与在初始化列表中的顺序无关。1
2
3
4
5
6
7
8
9class A
{
private:
int n1;
int n2;
public:
A(): n2(0), n1(n2 + 2)
{}
}
n1先于n2被声明,因此n1也会在n2之前被初始化,所以先会用n2+2去初始化n1.由于n2这个时候还没有被初始化,因此它的值是随机的。12.查找和排序
要求在排序的数组(或者部分排序的数组)中查找一个数字或者统计某个数字出现的次数,可以尝试使用二分查找算法。哈希表最主要的优点是能够在O(1)时间查找某一个元素,是效率最高的查找方式。但缺点是需要额外的空间来实现哈希表。能够从额外空间消耗、平均时间复杂度和最差时间复杂度等方面比较不同排序算法的优缺点。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//快速排序代码
int partition(int data[], int start, int end)
{
if(data == NULL || start < 0 || end < 0)
throw new std::exception("Invalid Parameters");
int small = start - 1;
for(int i = start; i < end; i++)
{
if(data[i] < data[end])
{
small++;
if(small != i)
{
int temp = data[small];
data[small] = data[i];
data[i] = temp;
}
}
}
small++;
int temp = data[small];
data[small] = data[end];
data[end] = temp;
return small;
}
void quickSort(int data[], int start, int end)
{
int idx = partition(data, start, end);
if(idx - 1 > start)
quickSort(data, start, idx - 1);
if(end > idx + 1)
quickSort(data, idx + 1, end);
}
在子数组data[start…end]中,partition维护了四个区域。data[start…small]区间内的值都小于等于data[end],data[small+1…i]的区间都大于data[end],data[i+1,end-1]区间内的所有值没有限制。data[end]为每一轮选取的数字。<=x>x无限制选取值start—>smallsmall+1—>ii+1—>end-1end13.C++标准库和std命名空间
C++是在C语言的基础上进行开发的,早期的C++并不完善,不支持命名空间也没有自己的编译器,而是将C++代码翻译成C代码,再通过C编译器完成编译。这个时候的C++仍然使用C语言的库,stdio.h、stdlib.h、string.h等头文件依然有效;此外C++也开发了一些新的库,增加了自己的头文件,例如:1iostream.h fstream.h complex.h
和C语言一样,C++头文件仍然以.h为后缀,它们所包含的类、函数、宏等都是全局范围的。后来C++引入了命名空间的概念,计划重新编写库,将类、函数、宏等都统一纳入一个命名空间,这个命名空间的名字就是std。std是standard的缩写,即为“标准命名空间”。但是这时已经有很多用老式C++开发的程序,它们的代码中并没有使用命名空间,直接修改原来的库会带来很严重的后果。所以提出了新的想法,保留原来的库和头文件,它们可以在C++中继续使用,然后再将原来的库复制一份,在此基础上稍加修改,把类、函数、宏等纳入命名空间std下,这就成为了新版C++标准库,这样共存在了两份功能相似的库。
为了避免头文件重命名,新版的C++库也对头文件的命名进行了调整,去掉了后缀.h,所以老式C++的iostream.h变为iostream,fstream.h变为了fstream。而对于原来的C语言头文件,也采用同样的方法,但在每个名字前要添加一个c字母,如stdio.h变为cstdio,stdlib.h变为cstdlib。
需要注意的是旧的C++头文件是官方所反对使用的,已明确提出不再支持,但是旧的C头文件仍然可以使用,以保持兼容性。