C/C++中【提高程序效率的9个简单方法】及【20个重点笔记】
程序员文章站
2022-05-03 18:05:42
一、程序效率的提升
(1)如果选择为if---if---if--,则应选择if--else if--else或者是switch-case;后两者比较的次数少,效率高;
例如:
if(1==a) {o...
一、程序效率的提升
(1)如果选择为if---if---if--,则应选择if--else if--else或者是switch-case;后两者比较的次数少,效率高;
例如:
if(1==a) {omething;}
if(2==a) {dosomething;}
if(3==a) {dosomething;}
以上三个if都得执行,但是下面的:
switch(a)
{
case 1:do something; break;
case 2:do something; break;
case 3:do something; break;
}
只会执行一个case语句,提高了效率。
(2)函数的参数传引用比传值的效率更高。尤其在参数为类的对象的时候;
bool compare(string s1)
bool compare(string *s1) //传指针;
bool compare(string &s1) //传引用;
bool compare(const string &s1) //常量引用,否则不能引用常量;
第一个函数(值传递),则在参数传递和函数返回时,需要调用string的构造函数和析构函数两次,而其他的三个函数---指针传递和引用传递--则不需要调用这两个个函数。因为指针和引用都不会创建新的对象。如果一个构造一个对象和析构一个对象的开销是庞大的,这就是会效率造成一定的影响。引用与使用普通值传递一样方便直观,同时具有指针传递的高效和能力。因为引用是一个变量的别名,对其操作等同于对实际对象操作,所以当你确定在你的函数是不会或不需要变量参数的值时,就大胆地在声明的前面加上一个const吧,就如最后的一个函数声明一样。同时加上一个const还有一个好处,就是可以对常量进行引用,若不加上const修饰符,引用是不能引用常量的。
(3)对于重载自定义的++i和i++,++i比i++的效率高;
原因: ++i在运算过程中不产生临时对象,返回的就是i,是个左值,类似++i=1这样的表达式是合法的,而i++在运算的过程中会产生临时对象,返回的是零时对象的值,是个右值,像i++=1这样的表达式是非法的;
对于内置类型,单独的i++和++i语句,现在的编译器基本上都会优化成++i,所以就没什么区别了。
例如:下面的链表内部迭代器
list::iterator& list::iterator::operator++()//前加
{
pnote = pnote->pnext;
return *this;
}
list::iterator list::iterator::operator++(int)//后加
{
iterator tmp(*this);
pnote = pnote->pnext;
return tmp;
}
从后加的方式可以知道,对象利用自己创建一个临时对象,然后改变自己的状态,并返回这个临时对象,而前加的方式直接改变自己的状态,并返回自己的引用!由于迭代器一般都是遍历容器的,大数应用于循环,所以如果该链表有100个元素,如下代码所示:
for(_singlelist::iterator it = list.begin(); it != list.end(); ++it)
{
//do something
}
for(_singlelist::iterator it = list.begin(); it != list.end(); it++)
{
//do something
}
那么第二个代码就要调用200多个函数这样程序的效率就不可忽视了!
(4)对于类的对象有赋值和初始化时优先选择初始化,因为这样可以避免生成临时对象,赋值会产生临时对象;
father f1;
father f2(f1); //直接初始化;
father f3=f1; //赋值初始化;
当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。创建临时对象就会调用构造函数和析构函数,多了两次函数调用,增减了开销。
(5)如果程序中cout使用次数很少或只用一次,则可以使用std::cout来节省空间。
因为这样比导入整个命名空间更经济;
(6)对于类的对象返回引用比返回对象的效率要高。
因为不会调用拷贝构造函数,生成临时对象;但是特别注意临时对象和局部变量不能返回引用;
(7)六、避免使用多重继承
在c++中,支持多继承,即一个子类可以有多个父类。书上都会跟我们说,多重继承的复杂性和使用的困难,并告诫我们不要轻易使用多重继承。其实多重继承并不仅仅使程序和代码变得更加复杂,还会影响程序的运行效率。这是因为在c++中每个对象都有一个this指针指向对象本身,而c++中类对成员变量的使用是通过this的地址加偏移量来计算的,而在多重继承的情况下,这个计算会变量更加复杂,从而降低程序的运行效率。而为了解决二义性,而使用虚基类的多重继承对效率的影响更为严重,因为其继承关系更加复杂和成员变量所属的父类关系更加复杂。
(8)减少除法的使用。
无论是整数还是浮点数运算,除法都是一件运算速度很慢的指令,在计算机中实现除法是比较复杂的。所以要减少除法运算的次数。我们可以通过移位,或者转化为乘法来做除法运算。
例如:4/2 ---- 4>>1;
if(a>b/c) ----if(a*c>b)
(9)将小粒度函数声明为内联函数(inline)
由于调用函数是需要保护现场,为局部变量分配内存,函数结束后还要恢复现场等开销,而内联函数则是把它的代码直接写到调用函数处,所以不需要这些开销,但会使程序的源代码长度变大。所以若是小粒度的函数,如下面的max函数,由于不需要调用普通函数的开销,所以可以提高程序的效率。
int max(int a, int b)
{
return a>b?a:b;
}
注意,pt是int值的地址并不意味着pt本身的类型是int。例如,在有些平台中,int类型是个2字节值,而地址是个4字节值。
3、逗号运算符
逗号运算符的特性有下面几个:
(1)它确保先计算第一个表达式,然后计算第二个表达式;
i = 20, j = 2 * i; // i set to 20, then j set to 40
(2)逗号表达式的值是第二部分的值。例如,上面表达式的值为40。在所有运算符中,逗号运算符的优先级是最低的。例如:
cats = 17, 240; //被解释为:(cats = 17), 240;
也就是说,将cats设置为17,后面的240不起作用。如果是:
cats = (17, 240); //cats=240。
4、下面的程序输出结果是多少?
#include
int main()
{
int x=2,y,z;
x *=(y = z = 5); printf(“%d\n”,x); //10
z = 3;
x ==(y = z); printf(“%d\n”,x); //10
x =(y == z); printf(“%d\n”,x); //1
x = (y&z); printf(“%d\n”,x); // 与运算 3
x = (y&&z); printf(“%d\n”,x); //1
y = 4;
x = (y | z); printf(“%d\n”,x);//或运算 7
x = (y || z); printf(“%d\n”,x);//1
return 0;
}
5.下面的程序输出结果是多少?
#include
main()
{
int b = 3;
int arr[] = {6,7,8,9,10};
int *ptr = arr;
*(ptr++)+=123;//先算等号右边再算左边的;
//*ptr=*ptr+_123; 即arr[0]=129;
//千万不要认为是:*(ptr++)=*(ptr++)+123;
//还有就是要认清++和--的作用;
printf(“%d,%d\n”,*ptr,*(++ptr)); //8 8
//printf函数的运算设计到进栈和出栈,运算顺序是从右向左;
//先算*(++ptr),后算*ptr;
printf("%d\n",arr[0]); //129
}
6、c++存储方案:c++三种,c++11四种
这些方案的区别就在于数据保留在内存中的时间。
自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,也即用到时分配内存,在执行完函数或代码块时,它们使用的内存被释放回收,且能够重复使用。c++有两种存储持续性为自动的变量。
静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在,且静态变量一开始就在内存中。c++有3种存储持续性为静态的变量。
动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为*存储(free store)或堆(heap)。
线程存储持续性(c++11):当前,多核处理器很常见,这些cpu可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。本书不探讨并行。
7、自己写string类注意事项:
(1)、是否把返回值类型声明为该类型的引用?并在结束前是否返回该实例自身的引用(保证连续赋值);
(2)、是否把参数设置为const &?(否则会调用拷贝构造函数,其次防止意外改变参数)
(3)、是否记得释放自身的内存?(否则会内存泄露,指针指向了其他的地方)
(4)、是否进行了实例对象的等值判断,是否为同一个实例?(否则释放实例的时候,将参数的内存页释放,找不到赋值对象引起错误)
8、c++的多态性分为静态多态和动态多态。
静态多态性:编译期间确定具体执行哪一项操作,主要是通过函数重载和运算符重载来实现的;
动态多态性:运行时确定具体执行哪一项操作,主要是通过虚函数来实现的。
9、虚函数原理考点,例如下面程序的输出是什么?
解答:
关于类占用的内存空间,有以下几点需要注意:
(1)如果类中含有虚函数,则编译器需要为类构建虚函数表,类中需要存储一个指针指向这个虚函数表的首地址,注意不管有几个虚函数,都只建立一张表,所有的虚函数地址都存在这张表里,类中只需要一个指针指向虚函数表首地址即可。
(2)类中的静态成员是被类所有实例所共享的,它不计入sizeof计算的空间
(3)类中的普通函数或静态普通函数都存储在栈中,不计入sizeof计算的空间
(4)类成员采用字节对齐的方式分配空间
答案:12(32位系统)或16(64位系统)
10、c++中可以重载的运算符:new/delete、new[]/delete[]、++等。
不可以重载的运算符:、.、::、?:、sizeof、typeid、.、**、不能改变运算符的优先级。
11、重载++和–时是怎么区分前缀++和后缀++的?
当编译器看到++a(先自增)时,它就调用operator++(a)/operator++();
但当编译器看到a++时,它就调用operator++(a, int) /operator++(int)
[有类型无变量是哑元]。即编译器通过调用不同的函数区别这两种形式。
12、在c++中,如果确定了某一个构造函数的创建过程,在该构造函数中如果调用了其它重载的构造函数,它将不会执行其它构造函数的初始化列表部分代码,而是执行函数体代码,此时已经退化成普通函数了。例子说明如下:
(1)c的结构体内不允许有函数存在,c++允许有内部成员函数,且允许该函数是虚函数。所以c的结构体是没有构造函数、析构函数、和this指针的。
(2)c的结构体对内部成员变量的访问权限只能是public,而c++允许public,protected,private三种。
(3)c语言的结构体是不可以继承的,c++的结构体是可以从其他的结构体或者类继承过来的。
以上都是表面的区别,实际区别就是面向过程和面向对象编程思路的区别:
c的结构体只是把数据变量给包裹起来了,并不涉及算法。
而c++是把数据变量及对这些数据变量的相关算法给封装起来,并且给对这些数据和类不同的访问权限。
c语言中是没有类的概念的,但是c语言可以通过结构体内创建函数指针实现面向对象思想。
13、可作为函数重载判断依据的有:参数个数、参数类型、const修饰符;
不可以作为重载判断依据的有:返回类型。
14、如何将一个小数分解成整数部分和小数部分?
要记得利用头文件中的库函数modf,下面是函数原型(记住一些实用的库函数,避免自己重写):
fractpart= double modf(double num, double *intpart); // 将num分解为整数部分intpart和小数部分fractpart(返回值决定)
15、易误解:
如果int a[5], 那么a与&a是等价的,因为两者地址相同。
解答:一定要注意a与&a是不一样的,虽然两者地址相同,但意义类型不一样,
&a是整个数组对象的首地址,int (*p)[5];因此&a+1相当于a的地址值加上sizeof(int) * 5,也就是a[5],下一个对象的地址,已经越界了;
而a是数组首地址,也就是a[0]的地址,&a[0];a[0]的类型是int,而a+1相当于a的地址加上sizeof(int),即a[1]的地址。
16、注意sizeof不是函数而是运算符,所以在计算变量所占用空间大小时,括号是可以省略的,但在计算类型大小时括号则不能省略:
比如int i = 0; 则
sizeof int; //error
sizeof i=4; //ok
17、中nchar()和char()、varchar()和nvarchar()的区分?
char[10]
是定长的,也就是当你输入的字符小于你指定的数目8时,它会再后面补空值-'\0'。当你输入的字符大于指定的数时,它会截取超出的字符。
varchar[10]
长度为 n 个字节的可变长度且非 unicode 的字符数据。n 必须是一个介于 1 和 8,000 之间的数值。存储大小为输入数据的字节的实际长度,而不是 n 个字节。所输入的数据字符长度可以为零。
例如输入“hello”,那么气长度不是10,而是5+1=6,
为什么“+1”呢?这一个字节用于保存实际使用了多大的长度。
nvarchar[10]
包含 n 个字符的可变长度 unicode 字符数据。n 的值必须介于 1 与 4,000 之间。字节的存储大小是所输入字符个数的两倍。所输入的数据字符长度可以为零。unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示。
例如两字段分别有字段值:我和coffee
那么varchar字段占2×2+6=10个字节的存储空间,而nvarchar字段占8×2=16个字节的存储空间。
从空间上考虑,用varchar合适;从效率上考虑,用char合适,关键是根据实际情况找到权衡点。
这三种从名字上看比前面三种多了个“n”。它表示存储的是unicode数据类型的字符。我们知道字符中,英文字符只需要一个字节存储就足够了,但汉字众多,需要两个字节存储,英文与汉字同时存在时容易造成混乱,unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示。nchar、nvarchar的长度是在1到4000之间。和char、varchar比较起来,nchar、nvarchar则最多存储4000个字符,不论是英文还是汉字;而char、varchar最多能存储8000个英文,4000个汉字。可以看出使用nchar、nvarchar数据类型时不用担心输入的字符是英文还是汉字,较为方便,但在存储英文时数量上有些损失。
所以一般来说,选择标准:如字段值只是英文可选择varchar,而字段值存在较多的双字节(中文、韩文等)字符时用nvarchar
优缺点:
char:可以看出,是利用空间换时间,处理比较方便,英文一个字节存储。
【存储很短的信息;固定长度的,比如人名,这些步长而且长度大体相当;十分频繁改变的column。】
varchar:空间效率高,节约内存;但是时间效率有所下降,容易出现内存碎片,尤其当更新时的数据比元数据量大时,比较繁琐。另外,varchar申请的时候不能过于康概,要按需索取。对于相同的字符串,其存储空间虽然相同,但是对于内存的消耗是不同的。
18、如何解决在构造函数里统计对象计数不准确的问题?
原因:可能因为拷贝构造函数而产生的对象,这样就不会调用构造函数。解决办法,提供一个显示的赋值构造函数处理计数问题。
student::student(const student &st)
{
num++;
}
19、快排中中值的选取:
(1)固定元素法:取第一个或者最后一个作为partition的pivot。
(2)随机元素法:随机生成作为partition的pivot。
(3)三数取中或者无数取中法;
20、快速排序为什么比堆排序效率高?
(1)比较的次数,堆排序比较的次数比快排多;有很多都是无谓的比较,快排每调整一次每个元素都是朝正确的位置进一步,但是堆排序不是,最后一个元素调整到第一个,下次可能又跳回到最后一个;随机性太大;
(2)cache局部性原理。堆排序都是不相邻的,数据极不友好,快排都是相邻的,移动的快,充分利用率cache。
缺点:快排是递归调用,消耗的空间较大。
(1)如果选择为if---if---if--,则应选择if--else if--else或者是switch-case;后两者比较的次数少,效率高;
例如:
if(1==a) {omething;}
if(2==a) {dosomething;}
if(3==a) {dosomething;}
以上三个if都得执行,但是下面的:
switch(a)
{
case 1:do something; break;
case 2:do something; break;
case 3:do something; break;
}
只会执行一个case语句,提高了效率。
(2)函数的参数传引用比传值的效率更高。尤其在参数为类的对象的时候;
bool compare(string s1)
bool compare(string *s1) //传指针;
bool compare(string &s1) //传引用;
bool compare(const string &s1) //常量引用,否则不能引用常量;
第一个函数(值传递),则在参数传递和函数返回时,需要调用string的构造函数和析构函数两次,而其他的三个函数---指针传递和引用传递--则不需要调用这两个个函数。因为指针和引用都不会创建新的对象。如果一个构造一个对象和析构一个对象的开销是庞大的,这就是会效率造成一定的影响。引用与使用普通值传递一样方便直观,同时具有指针传递的高效和能力。因为引用是一个变量的别名,对其操作等同于对实际对象操作,所以当你确定在你的函数是不会或不需要变量参数的值时,就大胆地在声明的前面加上一个const吧,就如最后的一个函数声明一样。同时加上一个const还有一个好处,就是可以对常量进行引用,若不加上const修饰符,引用是不能引用常量的。
(3)对于重载自定义的++i和i++,++i比i++的效率高;
原因: ++i在运算过程中不产生临时对象,返回的就是i,是个左值,类似++i=1这样的表达式是合法的,而i++在运算的过程中会产生临时对象,返回的是零时对象的值,是个右值,像i++=1这样的表达式是非法的;
对于内置类型,单独的i++和++i语句,现在的编译器基本上都会优化成++i,所以就没什么区别了。
例如:下面的链表内部迭代器
list::iterator& list::iterator::operator++()//前加
{
pnote = pnote->pnext;
return *this;
}
list::iterator list::iterator::operator++(int)//后加
{
iterator tmp(*this);
pnote = pnote->pnext;
return tmp;
}
从后加的方式可以知道,对象利用自己创建一个临时对象,然后改变自己的状态,并返回这个临时对象,而前加的方式直接改变自己的状态,并返回自己的引用!由于迭代器一般都是遍历容器的,大数应用于循环,所以如果该链表有100个元素,如下代码所示:
for(_singlelist::iterator it = list.begin(); it != list.end(); ++it)
{
//do something
}
for(_singlelist::iterator it = list.begin(); it != list.end(); it++)
{
//do something
}
那么第二个代码就要调用200多个函数这样程序的效率就不可忽视了!
(4)对于类的对象有赋值和初始化时优先选择初始化,因为这样可以避免生成临时对象,赋值会产生临时对象;
father f1;
father f2(f1); //直接初始化;
father f3=f1; //赋值初始化;
当用于类类型对象时,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化总是调用复制构造函数。复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。创建临时对象就会调用构造函数和析构函数,多了两次函数调用,增减了开销。
(5)如果程序中cout使用次数很少或只用一次,则可以使用std::cout来节省空间。
因为这样比导入整个命名空间更经济;
(6)对于类的对象返回引用比返回对象的效率要高。
因为不会调用拷贝构造函数,生成临时对象;但是特别注意临时对象和局部变量不能返回引用;
(7)六、避免使用多重继承
在c++中,支持多继承,即一个子类可以有多个父类。书上都会跟我们说,多重继承的复杂性和使用的困难,并告诫我们不要轻易使用多重继承。其实多重继承并不仅仅使程序和代码变得更加复杂,还会影响程序的运行效率。这是因为在c++中每个对象都有一个this指针指向对象本身,而c++中类对成员变量的使用是通过this的地址加偏移量来计算的,而在多重继承的情况下,这个计算会变量更加复杂,从而降低程序的运行效率。而为了解决二义性,而使用虚基类的多重继承对效率的影响更为严重,因为其继承关系更加复杂和成员变量所属的父类关系更加复杂。
(8)减少除法的使用。
无论是整数还是浮点数运算,除法都是一件运算速度很慢的指令,在计算机中实现除法是比较复杂的。所以要减少除法运算的次数。我们可以通过移位,或者转化为乘法来做除法运算。
例如:4/2 ---- 4>>1;
if(a>b/c) ----if(a*c>b)
(9)将小粒度函数声明为内联函数(inline)
由于调用函数是需要保护现场,为局部变量分配内存,函数结束后还要恢复现场等开销,而内联函数则是把它的代码直接写到调用函数处,所以不需要这些开销,但会使程序的源代码长度变大。所以若是小粒度的函数,如下面的max函数,由于不需要调用普通函数的开销,所以可以提高程序的效率。
int max(int a, int b)
{
return a>b?a:b;
}
二、20个重点笔记。
1、c++的const比c语言#define更好的原因?
首先,它能够明确指定类型,有类型检查功能。
其次,可以使用c++的作用域规则将定义限制在特定的函数[常函数]或文件中。
第三,可以将const用于更复杂的类型,比如数组和结构。
c语言中也有const,在c语言中分配内存,其与c++中const的区别是:一是作用域规则不同;另一个是,在c++中可以用const值来声明数组长度。在c++中const在没有取地址和加extern时,是不分配内存空间的,和#define有相同的效果,常量折叠。
注意:外部要能访问test.cpp中的const形式的n, 必须在test.cpp中定义的时候用extern限定。
test.cpp extern const int n = 100; //必须加extern #include using namespace std; extern const int n; int main() { cout << n << endl; return 0; }2、不能简单地将整数赋给指针,如下所示:
int *ptr; ptr = 0x12342342; // type mismatch在这里,左边是指向int的指针,但右边是一个整数。在c99标准发布之前,c语言允许这样赋值。但c++在类型一致方面的要求更严格,编译器将显示一条错误消息,通告类型不匹配。要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:
int *ptr; ptr = (int *) 0x12342342; // type now match这样,赋值语句的两边都是整数的地址,因此这样赋值有效。但是这样做很危险。随意的数值可能是指向的重要文件,这样就会是系统崩溃。
注意,pt是int值的地址并不意味着pt本身的类型是int。例如,在有些平台中,int类型是个2字节值,而地址是个4字节值。
3、逗号运算符
逗号运算符的特性有下面几个:
(1)它确保先计算第一个表达式,然后计算第二个表达式;
i = 20, j = 2 * i; // i set to 20, then j set to 40
(2)逗号表达式的值是第二部分的值。例如,上面表达式的值为40。在所有运算符中,逗号运算符的优先级是最低的。例如:
cats = 17, 240; //被解释为:(cats = 17), 240;
也就是说,将cats设置为17,后面的240不起作用。如果是:
cats = (17, 240); //cats=240。
4、下面的程序输出结果是多少?
#include
int main()
{
int x=2,y,z;
x *=(y = z = 5); printf(“%d\n”,x); //10
z = 3;
x ==(y = z); printf(“%d\n”,x); //10
x =(y == z); printf(“%d\n”,x); //1
x = (y&z); printf(“%d\n”,x); // 与运算 3
x = (y&&z); printf(“%d\n”,x); //1
y = 4;
x = (y | z); printf(“%d\n”,x);//或运算 7
x = (y || z); printf(“%d\n”,x);//1
return 0;
}
5.下面的程序输出结果是多少?
#include
main()
{
int b = 3;
int arr[] = {6,7,8,9,10};
int *ptr = arr;
*(ptr++)+=123;//先算等号右边再算左边的;
//*ptr=*ptr+_123; 即arr[0]=129;
//千万不要认为是:*(ptr++)=*(ptr++)+123;
//还有就是要认清++和--的作用;
printf(“%d,%d\n”,*ptr,*(++ptr)); //8 8
//printf函数的运算设计到进栈和出栈,运算顺序是从右向左;
//先算*(++ptr),后算*ptr;
printf("%d\n",arr[0]); //129
}
6、c++存储方案:c++三种,c++11四种
这些方案的区别就在于数据保留在内存中的时间。
自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,也即用到时分配内存,在执行完函数或代码块时,它们使用的内存被释放回收,且能够重复使用。c++有两种存储持续性为自动的变量。
静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在,且静态变量一开始就在内存中。c++有3种存储持续性为静态的变量。
动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为*存储(free store)或堆(heap)。
线程存储持续性(c++11):当前,多核处理器很常见,这些cpu可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。本书不探讨并行。
7、自己写string类注意事项:
cstring & cstring::operator=(const cstring &st) { if(this == & st) return * this; delete [] str; len = st.len; str = new char [len + 1]; strcpy(str,st.str); return *this; }主要注意一下四点:
(1)、是否把返回值类型声明为该类型的引用?并在结束前是否返回该实例自身的引用(保证连续赋值);
(2)、是否把参数设置为const &?(否则会调用拷贝构造函数,其次防止意外改变参数)
(3)、是否记得释放自身的内存?(否则会内存泄露,指针指向了其他的地方)
(4)、是否进行了实例对象的等值判断,是否为同一个实例?(否则释放实例的时候,将参数的内存页释放,找不到赋值对象引起错误)
8、c++的多态性分为静态多态和动态多态。
静态多态性:编译期间确定具体执行哪一项操作,主要是通过函数重载和运算符重载来实现的;
动态多态性:运行时确定具体执行哪一项操作,主要是通过虚函数来实现的。
9、虚函数原理考点,例如下面程序的输出是什么?
class a { public: virtual void funa(); virtual void funb(); void func(); static void fund(); static int si; private: int i; char c; };问:sizeof(a) = ?
解答:
关于类占用的内存空间,有以下几点需要注意:
(1)如果类中含有虚函数,则编译器需要为类构建虚函数表,类中需要存储一个指针指向这个虚函数表的首地址,注意不管有几个虚函数,都只建立一张表,所有的虚函数地址都存在这张表里,类中只需要一个指针指向虚函数表首地址即可。
(2)类中的静态成员是被类所有实例所共享的,它不计入sizeof计算的空间
(3)类中的普通函数或静态普通函数都存储在栈中,不计入sizeof计算的空间
(4)类成员采用字节对齐的方式分配空间
答案:12(32位系统)或16(64位系统)
10、c++中可以重载的运算符:new/delete、new[]/delete[]、++等。
不可以重载的运算符:、.、::、?:、sizeof、typeid、.、**、不能改变运算符的优先级。
11、重载++和–时是怎么区分前缀++和后缀++的?
当编译器看到++a(先自增)时,它就调用operator++(a)/operator++();
但当编译器看到a++时,它就调用operator++(a, int) /operator++(int)
[有类型无变量是哑元]。即编译器通过调用不同的函数区别这两种形式。
12、在c++中,如果确定了某一个构造函数的创建过程,在该构造函数中如果调用了其它重载的构造函数,它将不会执行其它构造函数的初始化列表部分代码,而是执行函数体代码,此时已经退化成普通函数了。例子说明如下:
class cbook { public: double m_price; cbook() { cbook(8.8); } cbook(double price) : m_price(price) { } }; int main() { cbook c; cout << c.m_price << endl; // 此时并不会输出理想中的8.8 }12、c的结构体和c++结构体的区别
(1)c的结构体内不允许有函数存在,c++允许有内部成员函数,且允许该函数是虚函数。所以c的结构体是没有构造函数、析构函数、和this指针的。
(2)c的结构体对内部成员变量的访问权限只能是public,而c++允许public,protected,private三种。
(3)c语言的结构体是不可以继承的,c++的结构体是可以从其他的结构体或者类继承过来的。
以上都是表面的区别,实际区别就是面向过程和面向对象编程思路的区别:
c的结构体只是把数据变量给包裹起来了,并不涉及算法。
而c++是把数据变量及对这些数据变量的相关算法给封装起来,并且给对这些数据和类不同的访问权限。
c语言中是没有类的概念的,但是c语言可以通过结构体内创建函数指针实现面向对象思想。
13、可作为函数重载判断依据的有:参数个数、参数类型、const修饰符;
不可以作为重载判断依据的有:返回类型。
14、如何将一个小数分解成整数部分和小数部分?
要记得利用头文件中的库函数modf,下面是函数原型(记住一些实用的库函数,避免自己重写):
fractpart= double modf(double num, double *intpart); // 将num分解为整数部分intpart和小数部分fractpart(返回值决定)
15、易误解:
如果int a[5], 那么a与&a是等价的,因为两者地址相同。
解答:一定要注意a与&a是不一样的,虽然两者地址相同,但意义类型不一样,
&a是整个数组对象的首地址,int (*p)[5];因此&a+1相当于a的地址值加上sizeof(int) * 5,也就是a[5],下一个对象的地址,已经越界了;
而a是数组首地址,也就是a[0]的地址,&a[0];a[0]的类型是int,而a+1相当于a的地址加上sizeof(int),即a[1]的地址。
16、注意sizeof不是函数而是运算符,所以在计算变量所占用空间大小时,括号是可以省略的,但在计算类型大小时括号则不能省略:
比如int i = 0; 则
sizeof int; //error
sizeof i=4; //ok
17、中nchar()和char()、varchar()和nvarchar()的区分?
char[10]
是定长的,也就是当你输入的字符小于你指定的数目8时,它会再后面补空值-'\0'。当你输入的字符大于指定的数时,它会截取超出的字符。
varchar[10]
长度为 n 个字节的可变长度且非 unicode 的字符数据。n 必须是一个介于 1 和 8,000 之间的数值。存储大小为输入数据的字节的实际长度,而不是 n 个字节。所输入的数据字符长度可以为零。
例如输入“hello”,那么气长度不是10,而是5+1=6,
为什么“+1”呢?这一个字节用于保存实际使用了多大的长度。
nvarchar[10]
包含 n 个字符的可变长度 unicode 字符数据。n 的值必须介于 1 与 4,000 之间。字节的存储大小是所输入字符个数的两倍。所输入的数据字符长度可以为零。unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示。
例如两字段分别有字段值:我和coffee
那么varchar字段占2×2+6=10个字节的存储空间,而nvarchar字段占8×2=16个字节的存储空间。
从空间上考虑,用varchar合适;从效率上考虑,用char合适,关键是根据实际情况找到权衡点。
这三种从名字上看比前面三种多了个“n”。它表示存储的是unicode数据类型的字符。我们知道字符中,英文字符只需要一个字节存储就足够了,但汉字众多,需要两个字节存储,英文与汉字同时存在时容易造成混乱,unicode字符集就是为了解决字符集这种不兼容的问题而产生的,它所有的字符都用两个字节表示,即英文字符也是用两个字节表示。nchar、nvarchar的长度是在1到4000之间。和char、varchar比较起来,nchar、nvarchar则最多存储4000个字符,不论是英文还是汉字;而char、varchar最多能存储8000个英文,4000个汉字。可以看出使用nchar、nvarchar数据类型时不用担心输入的字符是英文还是汉字,较为方便,但在存储英文时数量上有些损失。
所以一般来说,选择标准:如字段值只是英文可选择varchar,而字段值存在较多的双字节(中文、韩文等)字符时用nvarchar
优缺点:
char:可以看出,是利用空间换时间,处理比较方便,英文一个字节存储。
【存储很短的信息;固定长度的,比如人名,这些步长而且长度大体相当;十分频繁改变的column。】
varchar:空间效率高,节约内存;但是时间效率有所下降,容易出现内存碎片,尤其当更新时的数据比元数据量大时,比较繁琐。另外,varchar申请的时候不能过于康概,要按需索取。对于相同的字符串,其存储空间虽然相同,但是对于内存的消耗是不同的。
18、如何解决在构造函数里统计对象计数不准确的问题?
原因:可能因为拷贝构造函数而产生的对象,这样就不会调用构造函数。解决办法,提供一个显示的赋值构造函数处理计数问题。
student::student(const student &st)
{
num++;
}
19、快排中中值的选取:
(1)固定元素法:取第一个或者最后一个作为partition的pivot。
(2)随机元素法:随机生成作为partition的pivot。
(3)三数取中或者无数取中法;
20、快速排序为什么比堆排序效率高?
(1)比较的次数,堆排序比较的次数比快排多;有很多都是无谓的比较,快排每调整一次每个元素都是朝正确的位置进一步,但是堆排序不是,最后一个元素调整到第一个,下次可能又跳回到最后一个;随机性太大;
(2)cache局部性原理。堆排序都是不相邻的,数据极不友好,快排都是相邻的,移动的快,充分利用率cache。
缺点:快排是递归调用,消耗的空间较大。