C和C++关系
1、C++程序中调用C编译后的函数,为什么要加external “C”?
C++支持函数重载而C语言不支持函数重载,故函数被C++编译后和被C编译之后的名字不同;
引入external C用来解决函数名字匹配问题;
2、头文件ifdef/define/endif有什么用?
防止该头文件被重复引用
3、C和C++各自特点?
C是一种结构化语言,重点在于算法和数据结构。考虑如何通过一个过程(函数),对输入进行运算处理得到输出;
C++考虑如何构造一个对象模型,让模型能够契合与之对应的问题域,通过获取对象的状态信息得到输出或实现过程控制;
C/C++程序编译过程[1]
当我们进行编译的时候,要使用一系列的工具,我们称之为工具链,其中包括:
预处理器cpp
编译器gcc/g++
汇编器as
链接器ld
一个C/C++程序编译过程包括下面几个阶段:
预处理 预处理器cpp将对源文件中的宏进行展开。
编译 gcc将c文件编译成汇编文件。
汇编 汇编器as将汇编文件编译成机器码。
链接 链接器ld将目标文件和外部符号进行连接,得到一个可执行二进制文件。
下面以一个很简单的 hello.c 来探讨这个过程
#include
#define BUFSIZE 1024
int main(int argc, char *argv[])
{
char hello[BUFSIZE] = "Hello my friend!";
printf("%s\n", hello);
return 0;
}
### 预处理(预处理器 cpp)
[butbueatiful@xt myhello]$ gcc -E hello.c -o hello.i
或
[butbueatiful@xt myhello]$ cpp hello.c -o hello.i
我们用vi打开 hello.i 可以看到有如下内容:
......
int main(int argc, char *argv[])
{
char hello[1024] = "Hello my friend!";
printf("%s\n", hello);
return 0;
}
......
我们可以看到,文件中宏定义 BUFSIZE 出现的位置被 1024 替换掉了,其它的内容保持不变。
### 编译器将 .i 文件编译成汇编文件
[butbueatiful@xt myhello]$ gcc -S hello.i # 得到汇编文件hello.s
### 汇编器将汇编文件编译成机器码(汇编器 as)
[butbueatiful@xt myhello]$ gcc -c hello.s -o hello.o
或
[butbueatiful@xt myhello]$ as hello.s -o hello.o
hello.o中为目标机器上的二进制文件
用 nm 查看文件中的符号:
[butbueatiful@xt myhello]$ nm -a hello.o
00000000 b .bss
00000000 n .comment
00000000 d .data
00000000 n .note.GNU-stack
00000000 t .text
00000000 a hello.c
00000000 T main
U puts
既然已经是二进制目标文件了,能不能执行呢?
[butbueatiful@xt myhello]$ chmod +x hello.o
[butbueatiful@xt myhello]$ ./hello.o
-bash: ./hello.o: cannot execute binary file
其实这时 puts 前面的 U 表示这个符号的地址还没有定下来,T表示这个符号属于代码段。ld连接的时候会为这些带U的符号确定地址。
### 链接(链接器 ld)
链接需要指定库的位置。通常程序中会有很多的外部符号,因此需要指定的位置就会很多。
不过,我们只需要调用 gcc 即可,ld会自己去找这些库的位置。
[butbueatiful@xt myhello]$ gcc hello.o -o hello # 得到可执行文件hello
预处理、const与sizeof
const与#define相比有什么不同
const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能产生意料不到的错误;
有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中只能使用const常量而不使用宏常量,即const常量完全取代宏常量;
C++中const有什么作用
- 定义常量;
- 修饰函数形式参数;当输入参数为用户自定义类型和抽象数据类型时,应该将“值传递”改为“const &传递”,可以提高效率。
- const修饰函数的返回值;如给“指针传递”的函数返回值加const,则返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类型指针。
- const修饰类的成员函数;任何不会修改数据成员的函数都应用const修饰,这样,当不小心修改了数据成员或调用了非const成员函数时,编译器都会报错。int get(void)const;
sizeof和strlen之间的区别
- sizeof操作符的结果类型是size_t,在头文件中的typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小;
- sizeof是运算符,strlen是函数;
- sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以“\0”结尾的。sizeof还可以用函数做参数;
- 数组做sizeof的参数不退化,传递给strlen就退化为指针;
- 大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度;
- strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而不是类型占内存的大小;
- sizeof后如果是类型必须加括号,如果是变量名可以不加括号。这是因为sizeof是个操作符而不是个函数;
- 当使用了一个结构类型或变量时,sizeof返回实际的大小。当时用以静态的空间数组时,sizeof返回全部数组的尺寸。sizeof操作符不能返回被动态分配的数组或外部的数组的尺寸;
- 数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址。在C++中传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小。如果想在函数内知道数组大小,进入函数后用memcpy将数组复制出来,长度由另一个形参穿进去;
- 计算结构变量的大小就必须讨论数组对齐问题;
- sizeof操作符不能用于函数类型、不完全类型或位字段;
一个空类占多少空间?多重继承的空类?
class A
{};
class B
{};
class B :public A
{};
class C :public virtual B
{};
class D :public A, public B
{};
空类占空间1,多重继承的空类占空间1;虚继承涉及虚表(虚指针),占空间4;
内联函数和宏定义
内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数可以直接被镶嵌到目标代码中。而宏只是一个简单的替换。
内联函数要进行参数类型检查,这是内联函数跟宏相比的优势。
inline是指嵌入代码,就是在调用函数的地方不做跳转,而是把代码直接写到那里去。
inline一般用于以下情况:
- 一个函数不断被重复调用;
- 函数只有简单的几行,且函数内不包含for、while、switch;
指针与引用
指针和引用的差别
- 非空区别;引用不为空。
- 合法性区别;指针需测试,防止其为空,引用不需要测试合法性。
- 可修改区别;指针可以被重新赋值,引用指向初始化时被指定的对象,以后不能改变,但指向对象其内容可以改变。
- 应用区别;
以下情况应该使用指针:一是考虑到存在不指向任何对象的可能;二是需要能够在不同的时刻只想不同的对象;如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么应该用引用。
循环、递归与概率
STL模板与容器
STL有以下优点:
可以方便、容易地实现搜索数据或对数据排序等一系列算法;
调试程序时更加安全和方便;
STL一些概念定义:
模板类:正规名称叫泛型,一个类的模板叫做泛型类,而一个函数的模板叫泛型函数;
STL标准模板库:一些聪明人写的一些模板,现已成为每个人使用的标准C++语言中的一部分;
容器:可容纳一些数组的模板类,STL中有vector、set、map、multimap和deque等容器;
向量:基本数组模板;
游标(Iterator):这是一个奇特的东西,是一个指针,用来指向STL容器中的元素,也可以指向其他的元素;
面向对象
class和struct有什么区别
C语言的struct与C++的class区别:struct只是作为一种复杂数据类型定义,不能用于面向对象编程;
C++中的struct和class的区别:对于成员访问权限以及继承方式,class中默认的是private的,而struct中则是public的。class还可以用于表示模板类型,struct则不行;
编写类String的构造函数、析构函数和赋值函数
类String的原型:
class String
{
public:
//普通构造函数
String(const char *str=NULL);
//拷贝构造函数
String(const String &other);
//析构函数
~ String(void);
//赋值函数
String & operate=(const String &other);
private:
//用于保存字符串
char *m_data;
};
构造函数
String::String(ocnst cahr *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];
strcpy(m_data,other.m_data);
}
赋值函数
String & String::operate=(const String &other)
{
//检查自赋值
if(this==&other)
return *this;
//释放原有的内存资源
delete[] m_data;
//分配新的内存资源,并复制内容
int length=strlen(other.m_data);
m_data=new char[length+1];
strcpy(m_data,other.m_data);
//返回本对象的引用
return *this;
}
什么是多态
多态性可以简单概括为“一个接口,多种方法”;
重载和覆盖的区别
虚函数总是在派生类中被改写,这种改写被称为“override”(覆盖)。
override是指派生类重写基类的虚函数,重写的函数必须有一致的参数表和返回值;
overload约定成俗译为“重载”,是指编写一个与已有函数同名但是参数表不同的函数;
友元
友元函数是一种定义在类外部的普通函数,但需要在类体内进行说明,为与该类的成员函数加以区别,在说明前加以关键字friend。友元不是成员函数,但是可以访问类中的私有成员,其作用在于提高程序的运行效率,但是它破坏了类的封装性和隐藏性,使得非成员函数可以访问累的私有成员。
友元可以使一个函数,成为友元函数;也可以是一个类,称为友元类;
写一程序,设计一个点类Point,求两个点之间的距离
class Point
{
provate:
float x,y;
public:
point(float a=0.0f,float b=0.0f):x(a,b){};
friend float distance(Point &leftm,Point &right);
}
float diatance(Point &left,Point &right)
{
return ((left.x-right.x)^2+(left.y-right.y)^2)^0.5;
}
描述模板类的友元重载,代码实现
#include
using namespace std;
template
class Test;
template
ostream& operator<<(ostream& out, const Test &obj);
template
class Test
{
private:
int num;
public:
Test(int n=0)
{
num = n;
}
Test(const Test ©)
{
num = copy.num;
}
friend ostream &poerator << <>(ostream &out, const Test &obj);
};
template
ostream &operator<<(ostream &out, const Test &obj)
{
out << obj.num;
return out;
}
int main()
{
Test t(2);
cout << t << endl;
return 0;
}
继承与接口
位运算与嵌入式编程