C、C++的一些基本概念


1、常量表达式 :常量表达式是编译器在编译时就能够计算出结果的整型表达式。整型字面值常量是常量表达式,正如一个通过常量表达式自我初始化的 const 对象也是常量表达式一样。

2、用 class 和 struct 关键字定义类的唯一差别在于默认访问级别:默认情况下,struct 的成员为 public,而 class 的成员为 private。

3、inline函数和普通函数的区别,inline函数不需要函数调用的开销。一般都是比较小的表达式才使用inline函数。

4、inline函数和#define宏的区别。宏的替换是在预编译期进行的。只是进行简单的替换,不能进行类型的检查。inline函数是在编译器替换的,因此可以进行类型检测。两者最主要的区别就是编译时间的不同。

5、和内置类型的输入操作一样,string 的输入操作符也会返回所读的数据流。因此,可以把输入操作作为判断条件

eg:

int main()
{
string word;
// read until end-of-file, writing each word to a new line
while (cin >> word)
	cout << word << endl;
return 0;
}

只有在输入为ctrl+d时才会停止
6、指针和引用的区别

指针与引用看上去完全不同(指针用操作符“*”和“->”,引用使用操作符“. ”),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?

首先,要认识到在任何情况下都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。

 “但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”

char *pc = 0;          // 设置指针为空值

char& rc = *pc;        // 让引用指向空值

这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生)。应该躲开写出这样代码的人,除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。

因为引用肯定会指向一个对象,在C++里,引用应被初始化。

string& rs;             // 错误,引用必须被初始化

string s("xyzzy");

string& rs = s;         // 正确,rs指向s

指针没有这样的限制。

string *ps;             // 未初始化的指针

                        // 合法但危险

不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。

void printDouble(const double& rd)

{

    cout << rd;         // 不需要测试rd,它

}                       // 肯定指向一个double值

相反,指针则应该总是被测试,防止其为空:

void printDouble(const double *pd)

{

  if (pd) {             // 检查是否为NULL

    cout << *pd;

 }

}

指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。

string s1("Nancy");

string s2("Clancy");

string& rs = s1;          // rs 引用 s1

string *ps = &s1;         // ps 指向 s1

rs = s2;                 // rs 仍旧引用s1,

                       // 但是 s1的值现在是

                       // "Clancy"

ps = &s2;               // ps 现在指向 s2;

                       // s1 没有改变

总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。

还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。

vector<int> v(10);       // 建立整形向量(vector),大小为10;

                         // 向量是一个在标准C库中的一个模板(见条款M35)

v[5] = 10;               // 这个被赋值的目标对象就是操作符[]返回的值

    如果操作符[]返回一个指针,那么后一个语句就得这样写:

*v[5] = 10;

但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款M30)

当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。

 

7、函数传值问题。

函数传递指针其实也是传值,如果在函数中修改指针指向的地址的值,那么可以达到你的目的。如果要修改指针的值。则传递进来的指针的值在函数结束之后没有发送改变。此时可以传递 指针的指针,也就是2维指针来达到目的,原则上也是第一句话

char *GetMemory(char *p,int num)//此种方法结束之后不能够得到指向分配的内存地址的指针
{
	p=(char *)malloc(sizeof(char)*num);
}
char *GetMemory1(char **p,int num)
{
	*p=(char *)malloc(sizeof(char)*num);
}
char *GetMemory2(char *p,int num)
{
	p=(char*)malloc(sizeof(char)*num);
	return p;
}

后面两种情况都能够达到目的

8、

const 指针与指向const的指针

当使用带有const的指针时其实有两种意思。一种指的是你不能修改指针本身的内容,另一种指的是你不能修改指针指向的内容。听起来有点混淆一会放个例子上来就明白了。

先说指向const的指针,它的意思是指针指向的内容是不能被修改的。它有两种写法。

const int* p; (推荐)

int const* p;

第一种可以理解为,p是一个指针,它指向的内容是const int 类型。p本身不用初始化它可以指向任何标示符,但它指向的内容是不能被改变的。

第二种很容易被理解成是p是一个指向int的const指针(指针本身不能被修改),但这样理解是错误的,它也是表示的是指向const的指针(指针指向的内容是不能被修改的),它跟第一种表达的是一个意思。为了避免混淆推荐大家用第一种。

再说const指针,它的意思是指针本身的值是不能被修改的。它只有一种写法

int* const p=一个地址; (因为指针本身的值是不能被修改的所以它必须被初始化)

这种形式可以被理解为,p是一个指针,这个指针是指向int 的const指针。它指向的值是可以被改变的如*p=3;

还有一种情况是这个指针本身和它指向的内容都是不能被改变的,请往下看。

const int* const p=一个地址;

int const* const p=一个地址;

看了上面的内容是不是有点晕,没关系,你不用去背它,用的多了就知道了,还有个技巧,通过上面的观察我们不难总结出一点规律,是什么呢?也许你已经看出来了,什么!竟然没看也来,那只好还得听我唠叨了。这个规律就是: 指向const的指针(指针指向的内容不能被修改)const关健字总是出现在*的左边而const指针(指针本身不能被修改)const关健字总是出现在*的右边,那不用说两个const中间加个*肯定是指针本身和它指向的内容都是不能被改变的。有了这个规则是不是就好记多了。

什么还是晕,那就看下面的程序,你把它编译一下看看错误提示就明白了。
Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 #include <iostream>
 
 using namespace std;
 
 int main(int argc, char *argv[])
 {
     int a=3;
     int b;
     
     /*定义指向const的指针(指针指向的内容不能被修改)*/ 
     const int* p1; 
     int const* p2; 
     
     /*定义const指针(由于指针本身的值不能改变所以必须得初始化)*/ 
     int* const p3=&a; 
     
     /*指针本身和它指向的内容都是不能被改变的所以也得初始化*/
     const int* const p4=&a;
     int const* const p5=&b; 
     
      p1=p2=&a; //正确
      *p1=*p2=8; //不正确(指针指向的内容不能被修改)
     
      *p3=5; //正确
      p3=p1; //不正确(指针本身的值不能改变) 
     
      p4=p5;//不正确 (指针本身和它指向的内容都是不能被改变) 
      *p4=*p5=4; //不正确(指针本身和它指向的内容都是不能被改变) 
      
     return 0; 
 }


 9、拷贝构造函数和赋值构造函数的区别

什么时候用拷贝构造函数,和赋值构造函数:
(一)当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。
    如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。
    自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。
例子:
String a("hello");
String b("world");
String c = a; // 调用了拷贝构造函数,最好写成 c(a);
c = b; // 调用了赋值构造函数。!
本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。
(二)拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。
不同点:
拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。 
operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
还要注意的是拷贝构造函数是构造函数,不返回值;而赋值函数需要返回一个对象自身的引用,以便赋值之后的操作。

浅拷贝和深拷贝
在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。
    深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源,但复制过程并未复制资源的情况视为浅拷贝。
    浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错。

 

 

class CExample

{

    ...

    CExample(const CExample&); //拷贝构造函数

    CExample& operator = (const CExample&); //赋值符重载

    ...

};

 

//拷贝构造函数使用赋值运算符重载的代码。

CExample::CExample(const CExample& RightSides)

{

    pBuffer=NULL;

    *this=RightSides     //调用重载后的"="

}

//赋值操作符重载

CExample & CExample::operator = (const CExample& RightSides)

{

    nSize=RightSides.nSize; //复制常规成员

    char *temp=new char[nSize]; //复制指针指向的内容 

    memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));

 

    delete []pBuffer; //删除原指针指向内容  (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)

    pBuffer=temp;   //建立新指向

    return *this

}

//其实也没啥改进,但觉得这样的写法是不是更好?省了一个temp临时变量。
CExample & CExample::operator = (const CExample& RightSides)
{
    nSize=RightSides.nSize; //复制常规成员
 
    delete []pBuffer; //删除原指针指向内容  (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
    pBuffer=new char[nSize];   //建立新指向
    memcpy(pBuffer,RightSides.pBuffer,nSize*sizeof(char));
    return *this
}

10、将关键字 const 加在形参表之后,就可以将成员函数声明为常量:
double avg_price() const;
const 成员不能改变其所操作的对象的数据成员。const 必须同时出现在声
明和定义中,若只出现在其中一处,就会出现一个编译时错误。

 

 

11、class Screen; // declaration of the Screen class

这个声明,有时称为前向声明(forward declaraton),在程序中引入了类类型的 Screen。在声明之后、定义之前,类 Screen 是一个不完全类型(incompete type),即已知 Screen 是一个类型,但不知道包含哪些成员。

不完全类型(incomplete type)只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。

12、有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。有默认
构造函数的类类型的成员,以及 const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。

class ConstRef {
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
// no explicit constructor initializer: error ri is uninitialized
ConstRef::ConstRef(int ii)
{ // assignments:
i = ii; // ok
ci = ii; // error: cannot assign to a const
ri = i; // assigns to ri which was not bound to an object
}

记住,可以初始化 const 对象或引用类型的对象,但不能对它们赋值。在开始执行构造函数的函数体之前,要完成初始化。初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中。编写该构造函数的正确方式为
// ok: explicitly initialize reference and const members
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
 

 成员初始化的次序
每个成员在构造函数初始化列表中只能指定一次,这不会令人惊讶。毕竟,给一个成员两个初始值意味着什么?也许更令人惊讶的是,构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。第一个成员首先被初始化,然后是第二个,依次类推。初始化的次序常常无关紧要。然而,如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值