c++易错点和一些函数相关
1、关于类型长度
16位平台
char 1个字节8位
short 2个字节16位
int 2个字节16位
long 4个字节32位
指针 2个字节
32位平台
char 1个字节8位
short 2个字节16位
int 4个字节32位
long 4个字节
long long 8个字节
指针 4个字节
64位平台
char 1个字节
short 2个字节
int 4个字节
long 8个字节(区别,但是有的编译器是4个有的是8个)
long long 8个字节
指针 8个字节(区别)
2、如何判断一段程序是由C 编译程序还是由C++编译程序编译的
#ifdef __cplusplus
#define USING_C 0
#else
#define USING_C 1
#endif
int main() {
if (USING_C) {
cout << "using c.\n";
}
else {
cout << "using c++";
}
system("pause");
}
3、main 主函数执行完毕后,是否可能会再执行一段代码
使用_onexit回调函数
int f1() {
cout << "Hello ";
return 0;
}
int f2() {
cout << "World !";
system("pause");
return 0;
}
int main() {
//越晚写的越早调用
_onexit(f2);
_onexit(f1);
cout << "------------\n";
}
//输出
//------------
//Hello World !
4、一些函数的实现
下面的程序因为涉及到数组和指针其实有很多会让程序崩溃的地方,比如src长度小于num,比如dest长度小于num
但是原函数只是说
to avoid overflows, the size of the array pointed by destination shall be long enough to
诸如此类,没有处理这种情况,不知道应该咋处理比较合适(比如是应该输出提示呢还是应该帮它截断呢),这里就先不处理
//将src前count个字符拷贝到dest
void* Memcpy(void* dest, const void* src, size_t count) {
char* tmp = (char*)dest;
const char* s = (const char*)src;
while (count--) {
*tmp++ = *s++;
}
return dest;
}
//将ptr前num个字符替换为value,value实际会被转为unsigned char类型
void* Memset(void* ptr, int value, size_t num) {
assert(ptr != NULL);
char* tmp = (char*)ptr;
//原版好像就算超过了tmp的长度也继续赋值,只是建议ptr长度不小于num
while (num-- && *tmp != '\0') {
*tmp = (unsigned char)value;
tmp++;
}
return ptr;
}
//将src开始num个字符拷贝到dest位置,可以允许src和dest有覆盖
void* Memmove(void* dest, const void* src, size_t num) {
assert(dest != nullptr && src != nullptr);
char* tmp = (char*)dest;
char* tmp_src = (char*)src;
//当tmp++时已经是指向第二个字符
int i = 1;
for (; i < num; i++) {
tmp++;
tmp_src++;
}
//可能有覆盖,所以从后往前填充
while (i--) {
*tmp-- = *tmp_src--;
}
return tmp;
}
//将src复制到dest,并在该点停止
char* Strcpy(char* dest, const char* src) {
assert(dest != nullptr && src != nullptr);
char* address = dest;
while ((*dest++ = *src++) != '\0');
return address;
}
//将src前n个字符复制到dest,并在该点停止
char* Strncpy(char* dest, const char* src, int n) {
assert(dest != nullptr && src != nullptr);
char* address = dest;
while (n-- && *src != '\0') {
*address++ = *src++;
}
//若n!=strlen(src)+1,则没有把串末的'\0'拷贝给dest
*address = '\0';
return address;
}
//计算字符长度,不算入'\0'
int Strlen(const char* str) {
assert(str != NULL);
int count = 0;
while (*str++ != '\0') {
count++;
}
return count;
}
//将src连接到dest末尾
char* Strcat(char* dest, const char* src) {
assert(dest != nullptr && src != nullptr);
char* address = dest;
while (*dest != '\0') {
dest++;
}
while ((*dest++ = *src++) != '\0');
return address;
}
//将src前n个字符连接到dest末尾
char* Strncat(char* dest, const char* src, int n) {
assert(dest != nullptr && src != nullptr);
char* address = dest;
while (*dest != '\0') {
dest++;
}
while (n-- && *src != '\0') {
*dest++ = *src++;
}
//若n!=strlen(src)+1,则没有把串末的'\0'拷贝给dest
*dest = '\0';
return address;
}
//比较str1和str2,若前面若干个字符相同,某一个比较短,则短的为小者;
//否则按第一对不相等的字符的大小返回
//返回正数为str1大于str2,负数为小于,0为等于
int Strcmp(const char* str1, const char* str2) {
assert(str1 != nullptr && str2 != nullptr);
while (*str1 == *str2) {
//相等返回0
if (*str1 == '\0') return 0;
str1++;
str2++;
}
//前面都相等,短的为小
if (*str1 == '\0') return -1;
if (*str2 == '\0') return 1;
// 第一对不相等的字符,按字典序大的为大者
return *str1 - *str2;
}
//比较str1和str2前n个字符大小,规则同Strcmp
int Strncmp(const char* str1, const char* str2, int n) {
assert(str1 != nullptr && str2 != nullptr);
while (n-- && *str1 != '\0' && *str2 != '\0'&& *str1 == *str2) {
str1++;
str2++;
}
//前面都相等,短的为小
if (*str1 == '\0' && *str2 != '\0') return -1;
if (*str2 == '\0' && *str1 != '\0') return 1;
// 相等返回0,否则按字典序大的为大者
return *str1 - *str2;
}
5、什么时候要用指针的指针
void GetMemory( char *p ) {
p = (char *) malloc( 100 );
}
void Test( void ) {
char *str = NULL;
GetMemory( str );
//str还是NULL,出错
strcpy( str, "hello world" );
printf( str );
}
//应改为以下
void GetMemory( char **p ) {
*p = (char *) malloc( 100 );
//分配内存失败
if (*p == NULL) {
cout << "分配内存失败!";
}
}
void Test( void ) {
char *str = NULL;
GetMemory( &str );
strcpy( str, "hello world" );
printf( str );
//释放资源
if(str)
free(str);
str = NULL;
}
6、虚函数,虚函数表里面内存如何分配?
编译时若基类中有虚函数,编译器为该的类创建一个一维数组的虚表,存放是每个虚函数的地址。基类和派生类都包含虚函数时,这两个类都建立一个虚表。
执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。
每一个类都有虚表。虚表可以继承,如果子类没有重写虚函数,那么子类虚表中仍然会有该函数的地址,只不过这个地址指向的是基类的虚函数实现。
派生类的虚表中虚函数地址的排列顺序和基类的虚表中虚函数地址排列顺序相同。
当用一个指针/引用调用一个函数的时候,被调用的函数是取决于这个指针/引用的类型。即如果这个指针/引用是基类对象的指针/引用就调用基类的方法;如果指针/引用是派生类对象的指针/引用就调用派生类的方法,当然如果派生类中没有此方法,就会向上到基类里面去寻找相应的方法。这些调用在编译阶段就确定了。
当涉及到多态性的时候,采用了虚函数和动态绑定,此时的调用就不会在编译时候确定而是在运行时确定。不在单独考虑指针/引用的类型而是看指针/引用的对象的类型来判断函数的调用,根据对象中虚指针指向的虚表中的函数的地址来确定调用哪个函数。
7、 C++中哪些不能是虚函数?
- 普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
- 构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
- 理论上内联函数不能是虚函数,因为内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。但是实际可以通过编译,可能因为被声明为虚函数之后就没有内联?
- 友元函数 因为不可以被继承。
- 静态成员函数 只有一个实体,不能被继承。父类和子类共有。
8、隐式转换
9、编写C++中的两个类 一个只能在栈中分配空间 一个只能在堆中分配
class HeapOnly {
public:
HeapOnly() {
cout << "heap constructor." << endl;
}
void destroy() const {
delete this;
}
private:
~HeapOnly() {}
};
class StackOnly {
public:
StackOnly() {
cout << "stack constructor." << endl;
}
~StackOnly() {
cout << "stack destructor." << endl;
}
private:
void* operator new(size_t);
};
//测试
HeapOnly* h = new HeapOnly;
//错误:"HeapOnly::~HeapOnly()" 不可访问
// 因为在栈上分配的空间由操作系统释放,但是析构函数被声明为私有,因此无法通过栈分配
//HeapOnly h;
StackOnly s;
// 错误:函数 "StackOnly::operator new"不可访问
//因为在堆上分配的空间需要调用new函数,但是new被重载声明为私有,因此无法通过堆分配空间
//StackOnly* str = new StackOnly;
10、随机数
可以利用rand()函数,将生成0~RAND_MAX之间的数字,vs 64位环境下RAND_MAX为32767(规定至少为32767)
rand()的内部实现是用线性同余法做的,它不是真的随机数,因其周期特别长,故在一定的范围里可看成是随机的。
int rand(void);
//所在头文件: cstdlib
//生成0~100范围内的随机数
vector<int> randomData;
for (int i = 0; i < 10; i++) {
randomData.push_back(rand()%101);
}
//也可以定义一个random函数,生成start到end之间的随机数
int random(int start, int end) {
assert(start > end);
return start+rand()%(end-start+1);
}
但是用rand函数生成的伪随机数每次运行程序得到的结果都是一样的,这是因为随机种子没有改变。
如果想要每次都生成不一样的随机数,可以用srand()函数设置随机种子为系统时钟
//time()所在头文件:ctime
//srand为初始化随机数发生器,用于设置随机种子
void srand (unsigned int);
srand ((unsigned int)time(NULL));
也可以生成其他类型的随机数,如生成0~1内的随机浮点数
double random = (double)rand()/(double)RAND_MAX;
11、不能重载的运算符
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)
12、char*字符串的初始化
//定义一个指向字符串常量"abc"的指针
char* str1 = "abc";
//运行时出错,因为str1指向的是常量,不可修改
//str1[0] = 'b';
//与str1指向的地址相同,都是指向常量
const char* str2 = "abc";
//编译时就报错,因为str2[0]不可修改
//str2[0] = 'b';
//定义一个字符数组,str3 = &str3[0]始终成立
char str3[] = "abc";
//成功运行,因为str3为字符数组
str3[0] = 'b';
//报错,因为str3不是指针变量,不能改变
//str3 = str1;
//成功运行,因为str1是指针变量
str1 = str3;
//str4不可改变
const char str4[] = "abc";
13、大小端模式
大端模式: 数据的高字节存在低地址 数据的低字节存在高地址
小端模式: 数据的高字节存在高地址 数据的低字节存在低地址
//大小端模式的判断
//方法一:利用联合体所有成员的起始位置一致,
//对联合体中的int类型赋值,然后判断联合体中char类型的值的大小
void SysCheck()
{
union IsLitte_Endian
{
int i;
char c;
};
IsLitte_Endian Check;
Check.i = 1;
bool Flag=Check.c == 1; //Flag为true表示是小端模式,Flag为false表示为大端模式,此时Flag为true。
return;
}
//方法二:利用强制类型转换判断
//与共用体判断类似
bool IsLitte_Endian()
{
int Wvar = 0x12345678;
short *Pvar = (short*)&Wvar;
return 0x5678 == Pvar[0];//true则为小端模式
}
转自这里
上一篇: Ajax基础简介原理
下一篇: HTML5新标签与特性(笔记)
推荐阅读