- 指针和引用的区别
- 交换两个数,不使用第三个变量
- const与define相比有什么不同
- 循环队列
- union、struct、enum大小区别
- 编写类String的构造、析构、拷贝构造和赋值函数
- 拷贝构造函数
- volatile(JD笔试题)
- C++菱形继承问题(JD笔试题)
- 结构体的定义方式
- C++多线程
- 分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)
- this指针
- 内存的分配方式有几种
- 内存操作
- 利用位运算实现加法
- 判断计算机是大端存储还是小端存储
- 数据结构
- STL
这里主要是一些关于在笔试面试中遇到的一些问题,在学习中总结,在总结中学习
指针和引用的区别
- 非空区别:在任何情况下都不能使用指向空值的引用,指针可以为空 -合法性区别:在使用引用之前不需要测试它的合法性。相反,指针则应该总被测试,防 止其为空 -可修改区别:指针可以被重新赋值以指向另一个不同的对象,引用在初始化之后不可以在改变 -应用区别:在以下情况下应该使用指针,一是考虑到存在不指向任何对象的可能(在这种情况下可以设置指针为空),二是需要能够在不同的时候指向不同对象(可以改变指针指向)
交换两个数,不使用第三个变量
- 算术运算
void fun(int a,int b)
{
a = b - a;
b = b - a;
a = b + a;
}
- 指针地址运算
void fun(int *a, int *b)
{
if(a < b){
a = (int *)(b - a);
b = (int *)(b - ((int) a & 0x0000ffff));
a = (int *)(b + ((int) a & 0x0000ffff));
}else{
b = (int *)(a - b);
a = (int *)(a - ((int) b & 0x0000ffff));
b = (int *)(a + ((int) b & 0x0000ffff));
}
}
- 位运算
void fun(int a, int b)
{
a = a^b;
b = a^b;
a = a^b;
}
- 栈实现
void fun(int a,int b)
{
stack s;
Push(s,a);
Push(s,b);
a = Pop(s);
b = Pop(s);
}
const与define相比有什么不同
- C++语言可以用const定义常量,也可以用define定义常量,但是前者比后者有更多的优点
- const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意想不到的错误(边际效应)
- 有些集成化的调试工具可以对const常量进行调试,但不能对宏常量进行调试。在C++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量
- const定义的常量可被修改
const int a = 10;
*((int *)&a) = 12;
循环队列
- 入队和出队的操作
rear = (rear+1)%m;
front = (front+1)%m;
- 删除操作:front指针
- 插入操作:rear指针
-
判断队列已满
front == (rear+1)%m;
-
队列长度
(rear-front+m)%m;
union、struct、enum大小区别
- 枚举enum:大小实质上就是整形
typedef enum{
ANUnknow,ANShape,ANData}ANDataType;
sizeof(ANDataType) = 4;
- 联合union:大小则以定义中最大的数据类型为准
union AAA{
double d;
int i;
}
sizeof(AAA) = 8;
- 结构体struct:字节对齐原则
struct BBB{
double dd;
int i;
}
sizeof(BBB) = 16;//空结构体的大小为1字节
编写类String的构造、析构、拷贝构造和赋值函数
类原型:
class String{
public:
String(const char *str = NULL);//普通构造函数
String(const String &other);//拷贝构造函数
~String(void);//析构函数
String & operator =(const String &other);//赋值函数
private:
char *m_data;
};
- 普通构造函数
String::String(const char *str)
{
if(str == NULL)
{
m_data = new char[1];
*m_data = '\0';
}
else{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data, str);
}
}
- 析构函数
String::~String(void)
{
delete [] m_data;
}
- 拷贝构造函数
String::String(const String &other)
{
int length = strlen(other.m_data);
m_data = new char[length+1];
if(m_data == NULL)
{
printf("申请内存失败\n");
}else{
strcpy(m_data, other.m_data);
}
}
- 赋值函数
String & String::operator =(const String &other)
{
if(this == other)
{
return *this;
delete [] m_data;
}
int length = strlen(other.m_data);
m_data = new char[length+1];
if(m_data == NULL)
{
printf("申请内存失败\n");
}else{
strcpy(m_data,other.m_data);
}
return *this;
}
拷贝构造函数
使用场合:
- 新建一个类,并用另一个同类的对象对它初始化
- 当函数参数为类对象时,在调用时需要将实参对象传给形参
extern void show(Circle cir);
Circle cir1;
show(cir1);
- 当函数返回值是类对象时
extern Circle getCircle();
Circle cir = getCircle();
volatile(JD笔试题)
作用:作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份
- 一个参数既可以是const还可以是volatile吗?解释为什么? 答:是的,一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
- 一个指针可以是volatile 吗?解释为什么? 答:是的,尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时 ## mutable的作用 ## 在C++程序中,类里面的数据成员加上mutable后,修饰为const的成员变量,就可以修改它了
C++菱形继承问题(JD笔试题)
- 什么是菱形继承 假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C
- 产生的问题(二义性) 问题是:因为Liger多重继承了Tiger和Lion类,因此Liger类会有两份Animal类的成员(数据和方法),Liger对象”lg”会包含Animal基类的两个子对象。 所以,你会问Liger对象有两个Animal基类的子对象会出现什么问题?再看看上面的代码-调用”lg.getWeight()”将会导致一个编译错误。这是因为编译器并不知道是调用Tiger类的getWeight()还是调用Lion类的getWeight()。所以,调用getWeight方法是不明确的,因此不能通过编译。
- 解决方案 如果Lion类和Tiger类在分别继承Animal类时都用virtual来标注,对于每一个Liger对象,C++会保证只有一个Animal类的子对象会被创建
class Tiger : virtual public Animal { /* ... */ };
class Lion : virtual public Animal { /* ... */ } ;
因为Java不支持多继承,所以不会出现菱形继承问题。但是Java可以通过接口间接实现多重继承
Class Mule implements Horse,Donkey
{
/* Horse和Donkey是接口*/
}
结构体的定义方式
- 方式一:先定义结构体类型,在定义变量
struct Student{
Type data;
};
struct Student stu1,stu2;
- 方式二:在定义结构体的同时定义变量
struct Student{
Type data;
}stu1,stu2;
- 方式三:定义匿名结构体(只能一次性声明结构体变量,不能另外声明)
struct {
Type data;
}stu1,stu2;
- 使用typedef声明->推荐该方法
typedef struct{
Type data;
}Student;
Student stu1,stu2;
- 结构体的自引用
struct SELF_REF2{
Type data;
struct SELF_REF2 *b;
};
或者:
typedef struct SELF_REF3_TAG {
Type data;
struct SELF_REF3_TAG *b;
}SELF_REF3;
C++多线程
- 线程的创建
#include<pthread.h>
void* say_hello(void* arg)
{
cout<<"Hello....."<<endl;
}
pthread_t tids;
int ret = pthread_creat(&tips,NULL,say_hello,NULL);
如果线程调用到函数在一个类中,那必须将该函数声明为静态函数函数 因为静态成员函数属于静态全局区,线程可以共享这个区域,故可以各自调用
- 在线程调用函数时传入参数
void* say_hello(void* arg)
{
int i = *( (int *)arg );//对传入的参数进行强制转换
cout<<"Hello....."<<endl;
}
int ret = pthread_creat(&tips,NULL,say_hello,(void *)&i);
-
使用pthread_join等待线程结束
pthread_join(tips,NULL);
- 线程创建时属性参数的设置pthread_attr_t及join功能的使用
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; //线程栈的位置
}pthread_attr_t;
使用方法:
pthread_attr_t attr; //线程属性结构体,创建线程时加入的参数
pthread_attr_init( &attr ); //初始化
pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); //是设置你想要指定线程属性参数,这个参数表明这个线程是可以join连接的join功能表示主程序可以等线程结束后再去做某事,实现了主程序和线程同步功能
分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)
-
BOOL型变量
if(!var)
-
int型变量
if(var==0)
-
float型变量:
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <=EPSINON)
-
指针变量
if(var==NULL)
this指针
- This指针本质是一个函数参数,只是编译隐藏起来形式的,语法层面上的参数。this只能在成员函数中使用,全局函数、静态函数不能使用this。 实际上,成员函数默认第一个参数为T* const this
- this在成员函数的开始前构造,在成员的结束后清除。当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传递进去
A a;
a.func(10);
//编译器会编译成:
A::func(&a,10);
- this指针并不占用对象的空间 所有成员函数的参数,不管是不是隐含的,都不会占用对象的空间,只会占用参数传递时的栈空间,或者直接占用一个寄存器
- this指针是什么时候创建的 this在成员函数的开始前构造,在成员的结束后清
- this指针存放在何处?堆、栈、还是其他? this指针会因编译器不同而有不同的放置位置,可能是堆、栈,也有可能是寄存器
- this指针如何是传递给类中的参数的? 大多数编译器通过ecx寄存器传递给this指针,一般来说不同编译器都会遵从一致的传参规则,否则不同编译器产生的obj就无法匹配了
- 如果我们知道一个对象this指针的位置,可以直接使用吗? this指针只有在成员函数中才有定义,因此,你获得一个对象后,也不能通过对象使用this指针。 只有在成员函数中采用this指针的位置,通过&this获取,也可以直接使用
内存的分配方式有几种
- 从静态存储区域分配: 内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量
- 在栈上创建: 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
- 在堆上分配,亦称动态内存分配: 程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
内存操作
-
给绝对地址进行赋值操作
1.法一:
int *ptr; ptr = (int *)0x10000; *ptr = 1234;
2.晦涩的方法是:(建议使用第一中)
(unsigned int*)0x100000 = 1234;
-
程序跳转到绝对地址执行
1.//首先要将0x100000强制转换成函数指针,即: (void (*)())0x100000 //然后再调用它: *((void (*)())0x100000)(); 2.用typedef可以看得更直观些: ``` typedef void(*)() voidFuncPtr; *((voidFuncPtr)0x100000)(); ```
利用位运算实现加法
int Add(int a, int b)
{
if(b == 0)
return a;
int sum,carry;
sum = a ^ b;
carry = (a & b) << 1;
return Add(sum, carry);
}
判断计算机是大端存储还是小端存储
void checkCPU()
{
int i = 0x11223344;
char *p;
p = (char *)&i;
if(*p == 0x44)
{
printf("小端存储\n");
}else{
printf("大端存储\n");
}
}
## Socket编写步骤 ## 服务器程序编写步骤:
- 调用socket()函数创建一个用于通信的套接字
- 给已经创建的套接字绑定一个端口号,这一般通过设置网络套接口地址和调用bind()函数来实现。
- 调用listen()函数使套接字成为一个监听套接字
- 调用accept()函数来接受客户端的连接,这是就可以和客户端通信了。
- 处理客户端的连接请求。read()/write()
- 终止连接 close
客户端程序编写步骤:
- 调用socket()函数创建一个用于通信的套接字
- 通过设置套接字地址结构,说明客户端与之通信的服务器的IP地址和端口号
- 调用connect()函数来建立与服务器的连接。
- 调用读写函数发送或者接收数据。
- 终止连接。
数据结构
树的基本操作
- 定义
typedef struct BNode
{
char value;
struct BNode *left;
struct BNode *right;
} Node,*pNode,**ppNode;
```
- 获取二叉树叶子总数
int GetLeafeNum(pNode root)
{
if(root==NULL) //空节点叶子数为0
{
return 0;
}
else if(root->left==NULL&&root->right==NULL)
{
return 1;
}
return GetLeafeNum(root->left)+GetLeafeNum(root->right);
} ```
- 获取二叉树的高度(左右子树最高高度+1)
int GetHeight(pNode root)
{
if(root==NULL)
{
return 0;
}
// max(left,right)+1;
return (GetHeight(root->left)>=GetHeight(root->right)?(GetHeight(root->left)+1):(GetHeight(root->right)+1));
}
- 交换左右子树
void swap_tree(pNode root)
{
if(root==NULL)
return;
else
{
pNode temp=root->left;
root->left=root->right;
root->right=temp;
swap_tree(root->left);
swap_tree(root->right);
}
}
链表的基本操作
- 定义
typedef struct student{
int data;
struct student *next;
}node;
- 单链表逆置
node *reverse(node *head)
{
node *p1,*p2,*p3;
if(head == null || head->next == null)
return head;
p1 = head;
p2 = p1->next;
while(p2){
p3 = p2->next;
p2->next = p1;
p1 = p2;
p2 = p3;
}
head->next = NULL;
head = p1;
return head;
}
STL
string库
-
string::npos 1、std:: string ::npos的定义:
static const size_t npos = -1;
表示 size_t 的最大值( Maximum value for size_t ) ,如果对 -1 表示size_t的最大值 代码验证:
#include <iostream>
#include <limits>
#include <string>
using namespace std;
int main()
{
size_t npos = -1;
cout << "npos: " << npos << endl;
cout << "size_t max: " << numeric_limits<size_t>::max() << endl;
}//结果:4294967295
也就是说npos表示size_t的最大值
- std::string::npos使用 如果作为一个 返回值 (return value) 表示没有找到匹配项
if(idx == string::npos)
但是string::npos作为string的成员函数的一个 长度参数 时,表示“ 直到字符串结束 (until the end of the string)
tmpname.replace(idx + 1, string::npos, "xxx"); //string::npos作为长度参数,表示直到字符串结束
- 将字符串内容反转 string::rbegin()/string::rend();
string str ("now step live...");
string::reverse_iterator rit;
for ( rit=str.rbegin() ; rit < str.rend(); rit++ )
cout << *rit;
- string::resize() resize函数用来改变string的大小,如果size小于来string的size大小,则截取前size个字符,如果大于,则用空格补充