extern
用法?extern
修饰变量的声明
如果文件a.c
需要引用b.c
中变量int v
,就可以在a.c
中声明extern int v
,然后就可以引用变量v
。
extern
修饰函数的声明
如果文件a.c
需要引用b.c
中的函数,比如在b.c
中原型是int fun(int mu)
,那么就可以在a.c
中声明extern int fun(int mu)
,然后就能使用fun
来做任何事情。
就像变量的声明一样,extern int fun(int mu)
可以放在a.c
中任何地方,而不一定非要放在a.c
的文件作用域的范围中。
默认情况情况下函数都是extern
的, 除非使用static
对函数进行了隐匿
extern
修饰符可用于指示C
或者C++
函数的调用规范。
比如在C++
中调用C
库函数,就需要在C++
程序中用extern “C”
声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C
函数规范来链接。主要原因是C++
和C
程序编译完成后在目标代码中命名规则不同。
int
转字符串, 字符串转int
C++11
标准增加了全局函数std::to_string
std::stoi
/std::stol
/std::stoll
等等函数strcat
,strcpy
,strncpy
,memset
,memcpy
的内部实现?strcat
: char *strcat(char *dst, char const *src);
首先找到dst
的end
以src
的`作为结束标志, 将
src添加到
dst的
end`上
dst
必须有足够的空间保存整个字符串
dst
和src
都必须是一个由``结尾的字符串(空字符串也行)
dst
和src
内存不能发生重叠
头文件: #include
作用: 将dst
和src
字符串拼接起来保存在dst
上
注意事项:
函数实现:
Code
char *strcat (char * dst, const char * src){
assert(NULL != dst && NULL != src); // 源码里没有断言检测
char * cp = dst;
while(*cp )
cp++; /* find end of dst */
while(*cp++ = *src++) ; /* Copy src to end of dst */
return( dst ); /* return dst */
}
strcpy
: char *strcpy(char *dst, const char *src);
src
必须有结束符(``), 结束符也会被复制
src
和dst
不能有内存重叠
dst
必须有足够的内存
头文件:#include
作用: 将src
的字符串复制到dst
字符串内
注意事项:
函数实现:
char *strcpy(char *dst, const char *src){ // 实现src到dst的复制
if(dst == src) return dst; //源码中没有此项
assert((dst != NULL) && (src != NULL)); //源码没有此项检查,判断参数src和dst的有效性
char *cp = dst; //保存目标字符串的首地址
while (*cp++ = *src++); //把src字符串的内容复制到dst下
return dst;
}
strncpy
: char *strncpy(char *dst, char const *src, size_t len);
strncpy
把源字符串的字符复制到目标数组,它总是正好向 dst
写入 len
个字符。
如果 strlen(src)
的值小于 len
,dst
数组就用额外的 NULL
字节填充到 len
长度。
如果 strlen(src)
的值大于或等于 len
,那么只有 len
个字符被复制到dst
中。这里需要注意它的结果将不会以NULL
字节结尾。
头文件: #include
作用: 从src
中复制len
个字符到dst
中, 如果不足len
则用NULL
填充, 如果src
超过len
, 则dst
将不会以NULL
结尾
注意事项:
函数实现:
char *strncpy(char *dst, const char *src, size_t len)
{
assert(dst != NULL && src != NULL); //源码没有此项
char *cp = dst;
while (len-- > 0 && *src != '\0')
*cp++ = *src++;
*cp = '\0'; //源码没有此项
return dst;
}
memset
: void *memset(void *a, int ch, size_t length);
将参数a
所指的内存区域前length
个字节以参数ch
填入,然后返回指向a
的指针。
在编写程序的时候,若需要将某一数组作初始化,memset()
会很方便。
一定要保证a
有这么多字节
头文件: #include
作用:
函数实现:
void *memset(void *a, int ch, size_t length){
assert(a != NULL);
void *s = a;
while (length--)
{
*(char *)s = (char) ch;
s = (char *)s + 1;
}
return a;
}
memcpy
从 src
所指的内存地址的起始位置开始,拷贝n
个字节的数据到 dest
所指的内存地址的起始位置。
可以用这种方法复制任何类型的值,
如果src
和dst
以任何形式出现了重叠,它的结果将是未定义的。
头文件: #include
作用:
函数实现:
void *memcpy(void *dst, const void *src, size_t length)
{
assert((dst != NULL) && (src != NULL));
char *tempSrc= (char *)src; //保存src首地址
char *tempDst = (char *)dst; //保存dst首地址
while(length-- > 0) //循环length次,复制src的值到dst中
*tempDst++ = *tempSrc++ ;
return dst;
}
strcpy
和 memcpy
的主要区别:
复制的内容不同: strcpy
只能复制字符串,而 memcpy
可以复制任意内容,例如字符数组、整型、结构体、类等。
复制的方法不同: strcpy
不需要指定长度,它遇到被复制字符的串结束符才结束,所以容易溢出。`memcpy` 则是根据其第`3`个参数决定复制的长度,遇到
并不结束。
用途不同: 通常在复制字符串时用 strcpy
,而需要复制其他类型数据时则一般用 memcpy
浅复制:
只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“(浅复制)浅拷贝”,
换句话说,浅复制仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
深复制: 在计算机中开辟了一块新的内存地址用于存放复制的对象。
浅复制的问题:
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。
这时,如果B 中有一个成员变量指针已经申请了内存,那A 中的那个成员变量也指向同一块内存。
这就出现了问题:当B把内存释放了(如:析构),这时A 内的指针就是野指针了,出现运行错误。
C++
模板是什么,底层怎么实现的?编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;
编译器会对函数模板进行两次编译:
在声明的地方对模板代码本身进行编译,
在调用的地方对参数替换后的代码进行编译。
这是因为函数模板要被实例化后才能成为真正的函数,在使用函数模板的源文件中包含函数模板的头文件,如果该头文件中只有声明,没有定义,那编译器无法实例化该模板,最终导致链接错误。
模板可以重载返回值, 函数重载不行
如果我们试图通过在头文件中定义函数模板, 在cpp
文件中实现函数模板, 那么我们必须在在实现的那个cpp
文件中手动实例化, 也就是使用你需要使用的参数替换模板, 从而使得编译器为你编译生成相应参数的模板函数.
C
语言struct
和C++
struct
区别struct
在C语言
中:
是用户自定义数据类型(UDT)
;
只能是一些变量的集合体, 成员不能为函数
没有权限设置
一个结构标记声明后,在C
中必须在结构标记前加上struct
,才能做结构类型名;
struct
在C++
中:
是抽象数据类型(ADT)
,支持成员函数的定义,(能继承,能实现多态)。
增加了访问权限, 默认访问限定符为public
(为了与C
兼容),class
中的默认访问限定符为private
定义完成之后, 可以直接使用结构体名字作为结构类型名
可以使用模板
inline
吗?虚函数要求在运行时进行类型确定,而内敛函数要求在编译期完成相关的函数替换, 所以不能
虚函数用于实现运行时的多态,或者称为晚绑定或动态绑定。
内联函数用于提高效率, 对于程序中需要频繁使用和调用的小函数非常有用。它是在编译期间,对调用内联函数的地方的代码替换成函数代码。
概念
赋值初始化,通过在函数体内进行赋值初始化;
列表初始化,在冒号后使用初始化列表进行初始化。
这两种方式的主要区别在于:
对于在函数体中初始化,是在所有的成员函数分配空间后才进行的。对于类对象类型成员变量, 则是先调用零参数构造函数, 如果零参数构造函数不存在编译器将会报错.
列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式)。
快的原因: 所以对于列表初始化: 只进行了一次初始化操作, 而赋值初始化则先进性了一次初始化,然后调用了一次复制构造函数.
一个派生类构造函数的执行顺序如下:
虚基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。
基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。
类类型的成员对象的构造函数(按照初始化顺序)
派生类自己的构造函数。
必须使用成员初始化的四种情况
当初始化一个引用成员时;
当初始化一个常量成员时;
基类, 无零参数构造函数时
成员类, 无零参数构造函数时
成员初始化列表做了什么
编译器在调用用户代码之前, 会按照类成员声明顺序一一初始化成员变量, 如果成员初始化类别中有初值,则使用初值构造成员函数.
初始化顺序由类中的成员声明顺序决定的,不是由初始化列表的顺序决定的;
首先是没必要使用虚函数:
由于使用间接调用(通过引用或则指针)导致类类型不可信, 而使用虚函数机制完成正确的函数调用.
但是构造函数本身是为了初始化对象实例, 创建对象必须制定它的类型, 其类类型是明确的, 因此在编译期间即可确定调用函数入口地址
因而没必要使用虚函数, 其调用在编译时由编译器已经确定.
其次不能使用虚函数:
虚函数的调用依赖于虚函数表, 虚函数表储存于静态储存区, 在存在虚函数的对象中都将插入一个指向虚函数表的指针,
在对象中插入一个指向虚函数表的指针是由构造函数完成的, 也就是说在调用构造函数时并没有指向虚函数表的指针, 也就不能完成虚函数的调用.
C++
中基类采用virtual
虚析构函数是为了防止内存泄漏。C++
中基类的析构函数应采用virtual
虚析构函数。~
以区别于构造函数,其不带任何参数, 也没有返回值. 也不允许重载.Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8