linux c/c++面试知识点整理(四)
31、printf输出时在%和字母之间插入数字表示场宽的规则?
当实际长度不够时, 右对齐;
如果字符串或者整数的长度超过说明的场宽, 则按其实际长度输出;
如果是浮点数, 若整数部分超过了说明的整数位场宽, 则按其实际长度输出, 若是小数部分超过了说明的小数位场宽, 则按说明的宽度以四舍五入输出。
32、逗号表达式?
例如:printf(“%d %d %d\n”, (a,b,c),b,c);
那么将输出才c,b,c这3个值,因为逗号表达式的值就是表达式中最后一个表达式的值;
即:表达式1,表达式2,表达式3…表达式n 就是表达式n的值。
33、c语言中标识符第一次字符必须是什么?
第一个字符必须是字母或者下划线,不能是数字。
34、数据流程图(DFD图)是什么
DFD图是结构化方法的需求分析工具。
35、hash_set和set的区别?
hast_set以hashtable为底层机制,而set以RB-tree(红黑树)为底层机制;
set有元素自动排序功能,而hash_set没有;
set可在logN下完成查找、插入和删除等操作,hash_set可在常数时间复杂度下完成这些操作,但是取决于哈希表的负载情况;
hast_multiset则允许键值重复;
36、static的用途以及类中使用static的规则。
用途:
static限制变量的作用域;
static不显示的初始化时,会被隐式的初始化为0;
static设置变量的存储域,变量存储在静态区;
类中使用static的规则:
不能通过类名来调用类的非静态成员函数,可以调用静态成员函数;
类的对象可以使用静态成员函数和非静态成员函数;
类的静态成员函数中不能使用类的非静态成员,因为此时静态成员函数已经分配了存储空间,而非静态成员却还没有分配内存,相当于变量声明了但是未定义就直接使用;
类的非静态成员函数(包括常函数)可以使用类的静态成员函数和静态成员变量,并且非静态成员常函数可以修改静态成员变量;
类的静态成员变量必须初始化以后才能使用;
37、数据库三范式
第一范式:数据库表中的所有字段值都是不可分解的原子值,比如地址字段,根据需求拆分成省份和城市更方便
第二范式:在一个数据库表中,一个表中只能保持一种数据,不可以把多种数据保存在同一张数据库表中,比如订单信息和商品信息就要分为两个表
第三范式:每一列数据都和主键直接相关,而不能间接相关,就是说字段值要和主键有直接关系
巴斯-科德范式:第三范式的一个子集,在第一范式基础上,任何非主属性不能对主键子集依赖。
38、网络编程中设计并发服务器,使用多进程和多线程,请问有什么区别?
1)两者都可以提高程序的并发度,提高程序运行效率和响应时间
2)线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进程正相反。
39、可以用作switch的参数的类型
int、short、byte、char、long
基本上可以转换为整数的类型都可以用作switch的参数。
40、linux使用多线程的方法
互斥锁、信号量、条件变量、全局变量、读写锁。
1)互斥锁:当线程A锁定了互斥变量时,线程B再去锁定时就会被挂起,直到A解锁。
注意:当线程要不断的去轮询检查某个条件以判断是否可以操作需同步的数据时,可使用条件变量提高效率。
demo如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex;
void *print_msg(void *arg)
{
int i = 0;
pthread_mutex_lock(&mutex); //互斥锁加锁
for (i = 0; i < 20; i++)
{
printf(" i = %d\n", i);
usleep(200);
}
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t id1, id2;
pthread_mutex_init(&mutex, NULL);
pthread_create(&id1, NULL, print_msg, NULL);
pthread_create(&id2, NULL, print_msg, NULL);
pthread_join(id1, NULL); //使主线程等待该线程结束后才结束,否则主线程很快结束,该线程没有机会执行
pthread_join(id2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
2)信号量:实际是一个整数,只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减1。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加1,但是sem_wait返回之前还是会将此value值减1。
demo如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
void *thread_func(void* msg);
sem_t sem;
sem_t sem_add;
#define MSG_SIZE 512
int main()
{
int res = -1;
pthread_t thread;
void *thread_result = NULL;
char msg[MSG_SIZE];
res = sem_init(&sem, 0, 0);
if (res == -1)
{
printf("sem init failed\n");
exit(-1);
}
res = sem_init(&sem_add, 0, 1);
if (res == -1)
{
printf("sem_add init failed\n");
exit(-1);
}
res = pthread_create(&thread, NULL, thread_func, msg);
if (res != 0)
{
printf("pthread_create failed\n");
exit(-1);
}
printf("input some text. Enter 'end' to finish...\n");
sem_wait(&sem_add);
while(strcmp("end\n", msg) != 0)
{
if (strncmp(msg, "TEST", 4) == 0)
{
strcpy(msg, "copy_data\n");
sem_post(&sem);
sem_wait(&sem_add);
}
fgets(msg, MSG_SIZE, stdin);
sem_post(&sem); //sem信号量加1,让子线程开始执行
sem_wait(&sem_add); //sem_add信号量减1,等待子线程处理完成
}
printf("waiting for thread to finish...\n");
res = pthread_join(thread, &thread_result);
if (res != 0)
{
printf("pthread_join faild\n");
exit(-1);
}
printf("thread joined\n");
sem_destroy(&sem);
sem_destroy(&sem_add);
exit(0);
return 0;
}
void* thread_func(void* msg)
{
char *ptr = (char*)msg;
sem_wait(&sem);
while(strcmp("end\n", ptr) != 0)
{
int i = 0;
for (; ptr[i] != '\0'; ++i )
{
if (ptr[i] >= 'a' && ptr[i] <= 'z')
{
ptr[i] -= 'a' - 'A';
}
}
printf("you input %d characters\n", i - 1);
printf("to uppercase: %s\n", ptr);
sem_post(&sem_add);
sem_wait(&sem);
}
sem_post(&sem_add);
pthread_exit(NULL);
}
3)条件变量:经常和互斥锁一起使用,使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会解开相应的互斥锁并等待条件发生变化,一旦其他的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此变量阻塞的线程,这些线程将重新锁定互斥锁并重新测试条件是否满足。
pthread_cont_init()
pthread_cont_destroy()
pthread_cont_wait() //线程解开mutex指向的锁并被条件变量阻塞
pthread_cont_timedwait() //多了时间参数,当时间过了以后,即使条件变量不满足,阻塞也被解除
pthread_cont_signal()/pthread_cont_broadcast //唤醒被条件变量阻塞的线程。
demo见我的另外一篇文章:
linux c/c++ 面试题目整理(四)中的第34题
4)读写锁:可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。
当读写锁是写加锁状态时,在这个锁被解锁前,所有试图对这个锁加锁的线程都会被阻塞;
当读写锁是读加锁状态时,其他线程可以读模式得到访问权,但是以写模式对它进行加锁的线程都将被阻塞;
当读写锁是在读模式加锁状态时,如果有其他线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,避免读模式锁长期占用,而写模式所长期阻塞;
读写锁适用于对数据读的次数比写的次数多的情况。
API接口:
初始化和销毁:
int pthread_rwlock_init();
int pthread rwlock_destroy();
读加锁、写加锁、解锁:
pthread_rwlock_rdlock();
pthread_rwlock_wrlock();
pthread_rwlock_unlock();
非阻塞获得读锁和写锁:
pthread_rwlock_tryrdlock();
pthread_rwlock_trywrlock();
demo如下
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#define Read_Num 2
pthread_rwlock_t lock;
class Data
{
public:
Data(int i, float f): I(i), F(f){}
int GetI()
{
return I;
}
float GetF()
{
return F;
}
private:
int I;
float F;
};
Data *pData = NULL;
void *read(void *arg)
{
int *id = (int*)arg;
while(true)
{
pthread_rwlock_rdlock(&lock);
printf(" reader %d is reading data\n", *id);
if (pData == NULL)
{
printf("data is NULL\n");
}
else
{
printf("data: I = %d, F = %f\n", pData->GetI(), pData->GetF());
}
pthread_rwlock_unlock(&lock);
}
pthread_exit(0);
}
void *write(void *arg)
{
while(true)
{
pthread_rwlock_wrlock(&lock); //写锁加锁后,解锁前,所有试图对该锁加锁的线程都会被堵塞
printf("writer is wiriting data:\n");
if (pData == NULL)
{
pData = new Data(2, 2.2);
printf("writer is writing data: %d, %f\n", pData->GetI(), pData->GetF());
}
else
{
delete pData;
pData = NULL;
printf("wirter free the data\n");
}
pthread_rwlock_unlock(&lock);
}
pthread_exit(0);
}
int main()
{
pthread_t reader[Read_Num];
pthread_t writer;
for (int i = 0; i < Read_Num; i++)
{
pthread_create(&reader[i], NULL, read, (void*)&i);
}
pthread_create(&writer, NULL, write, NULL);
while(1)
{
sleep(1);
}
return 0;
}