C++ 基础知识(一)

1.#inclue
#inclue<iostream>中的 # 是预处理标志,要最先处理,即在编译器编译代码之前运行
#inclue<>   和 #include “” 的区别:

< >引用的是编译器的类库路径里面的头文件
" "引用的是你程序目录的相对路径中的头文件
< >一般是引用自带的一些头文件:stdio.h、conio.h、string.h、stdlib.h等等之类的
" "一般是用来引用自己写的一些头文件
如果使用" ",会先在你项目的当前目录查找是否有对应头文件
如果没有,它还是会在对应的类库路径目录里面查找对应的头文件


2.iostream是一个标准库,里面包含了cin和cout,用法就是std::cout<<"hello";
int x;
std:cin>>x;

还可以这样:
int a,b,c;
std::cin>>a>>b>>c;
std::cout<<a<<" "<<b<<"\n"<<c<<"ddd";
"\n"代表换行,和std:endl是一个意思,但是后者还会刷新缓存区,两者基本一样

3.std是标准的意思。
namespace是命名空间。
<iostream>是头文件。//注意iostream.h
cout,cin,endl是在std命名空间中定义的对象。

4.C++中cout定义在命名空间std中,因此,在使用cout时可以用std::cout这样的方式使用,需要注意的是,cout是ostream类型的对象,而ostream类型定义在iostream头文件中,因此,在使用cout时,还需要使用#include<iostream>. 总结:C++中的命名空间和C#中的意思基本一样,也是相同的作用,用法也差不多,C++中的std是包含了所有的标识符的命名空间,所以一般都要using std,当然也可以直接std::cout 这样调用。

5.system("pause");是暂停的意思,在main函数最后可以不让控制台关闭,用Ctrl+F5也不会关闭,F5调试的话又不加system("pause")的话就会自动关闭。

6.#include "stdafx.h" ???

7.#include<iostream>和<iostream.h>的区别:
#include<iostream>中的iostream是C++标准头文件库,而#include<iostream.h>是C中的头文件库,因为C++继承了C的特性,所以也保留了iostream.h这种写法,因为C++引入了namespace,所以用iostream的时候就要开辟命名空间,例如:
#include<iostream>
using namespace std;
用iostream.h的时候就不用命名空间;


8.C++的编译器是从上到下的,例如这段代码
int _tmain()
{
    std::cout <<add(6,9);
    return 0;
}
int add(int x, int y)
{
    return x + y;
}
编译器会报错,但是把add放在上面就不会出错,C++的函数可以不用声明直接定义,但是这不是一个好习惯,最好先声明,原因很简单,声明后编译器就知道这个函数的存在了。声明的时候没有给函数分配内存,而定义就会函数分配内存



9.class类的成员函数的声明和定义不建议全部放在类内,最好定义要放在外面,要加上域符号::
为什么要声明和定义分开?因为写在一起就比如void p(){std::cout“111”;};
这样一个函数其实被称为内联函数,就是相当于把函数实现部分拷贝到调用函数的地方,但是一旦{}里面的代码很大,拷贝就会浪费性能,所以声明和定义最好分开,这样就不会去拷贝了

10.头文件用<>包括表示绝对路径,用""表示相对路径,include头文件就可以认为cpp文件包含了头文件中的所有代码。
为什么要用头文件的原因:
(1).可以被多个cpp文件同事引用,(2).类的使用者不关心类的细节,只要阅读头文件就可以知道所有信息


11.析构函数和构造函数都不能有返回值,析构函数不能带参数,一个类只能有一个析构函数,而构造函数可以重载,可以带参数。

12.析构函数会在程序结束时执行

13.int *p;    p = 0;  第二句是将p变为空指针; 这样更安全


14.double *p;这是一个double类型的指针,所以这个指针的内存为8个字节,
int *q 这是一个int类型的指针,内存为4字节,而且两者不能相互转换
指针的类型必须与变量的类型相匹配,不然会报错

15.int a = 1;
    int* p;  //int类型指针
    p = &a;  
    cout << *p<<endl; //取指针代表的地址的值,即a的值为1
    cout << p << endl;; //指针p的值 为0057fe10
    cout << &p<<endl;   //指针的地址 0057fe04
    cout << &a << endl;  //a变量的地址,即p的值 0057fe10


16.typedef 的意思就是把。。替换成。。;
double a=9.0 ; double *p=&a;  *p就代表去取指针存放的地址所指向的值,即*p=9.0;


17.为要使用指针:
1.处理堆中存放的大型数据
2.快速访问类的成员数据和函数
3.以别名的方式向函数传递参数

 

18.指针*号靠近变量还是靠近类型:
int   *a;
int*   a;
首先这两种其实是一样的,无伤大雅,但更建议使用第一种,靠近变量
两者意思相同且后者看上去更为清楚:a被声明为类型为 int* 的指针. 但是,这并不是一个好技巧,原因如下:
int* b, c, d;
人们很自然地以为这条语句把所有三个变量声明为指向整形的指针, 但事实上并非如此. 我们被它的形式愚弄了. 星号实际上是表达式 *b 的一部分, 只对这个标识符有用. b 是一个指针, 但其余两个变量只是普通的整形. 要声明三个指针, 正确的语句如下:
int *b, *c, *d;

 

 

19.引用&
int  &ra=a; 引用就是别名,就象库里的小名叫小学生,两者都是指库里
C++之所以增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能。
引用就是常量,只能进行初始化,不能赋值  即int &ra=a;而不能写成 int &ra;ra=a;
引用也可以是类的引用,即代表该类的别名
引用的自身的地址和本尊是一样的,只能修改其保存的值,而不能修改本身

引用很容易与指针混淆,它们之间有三个主要的不同:
1.不存在空引用。引用必须连接到一块合法的内存。
2.一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
3.引用必须在创建时被初始化。指针可以在任何时间被初始化。


20.传参三种方式:
int a=3;int b=4;
(1)按值传递  void swap(int a,int b);传进去之后不改变原来函数外a和b的值,跟C#一样,按值传递会在函数内部创建一个对象的副本,返回的也是一个对象的副本,所以外面的值不会改变.
(2)按地址传递   void swap(int *a,int *b);传入后会改变原来a和b的值
(3)按引用(别名)传递   void swap(int &a,int &b);传入后会改变原来a和b的值,这个&就相当于C#中的ref
尽量使用按别名传递的方式,这样能解决创建副本的问题,也能节省性能

下面的例子,会输出
a=11 b=22
a=22 b=11
a=22 b=11

#include<iostream>
void swap0(int a, int b) //按值传递
{
	int temp = a;
	a = b;
	b = a;
}

void swap1(int& a, int& b)//按引用传递
{
	int temp = a;
	a = b;
	b = temp;
}

void swap2(int* a, int *b)//按地址传递
{
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int a = 11; int b = 22;
	swap0(a, b);
	std::cout << "a=" << a << " " << "b=" << b << std::endl;

	a = 11; b = 22;
	swap1(a, b);
	std::cout << "a=" << a << " " << "b=" << b << std::endl;

	a = 11; b = 22;
	swap2(&a, &b);
	std::cout << "a=" << a << " " << "b=" << b << std::endl;
	return 0;
}

 

21.c++为什么要产生副本?通过汇编理解指针,学习汇编。。

22.栈区和堆区
1.栈区:由编译器自动分配并且自动释放,一般是函数参数和局部变量
2.堆区:一般有程序员分配释放,若不释放,则程序结束时由系统释放
堆是有一串不连续的链表串联起来的内存,速度较慢,容易产生内存碎片,容易使用,在申请内存的时候,系统会遍历一个空闲地址的链表,找到合适的地址分配给使用者
栈是系统自动分配的,程序员不能操作,而且一般较小,一般是函数参数或者局部变量,当函数结束时会释放,栈中的内存是先进后出的。一般第一个进栈的是函数下一行的内存地址,然后从右到左的函数参数,然后是变量,出栈时完全相反,所有变量出栈之后就剩函数下一行的内存地址了,然后自动跳转到下一行执行!


23.内存的三种分配方式:
1. 从静态存储区分配:此时的内存在程序编译的时候已经分配好,并且在程序的整个运行期间都存在。全局变量,static变量等在此存储。
2. 在栈区分配:相关代码执行时创建,执行结束时被自动释放。局部变量在此存储。栈内存分配运算内置于处理器的指令集中,效率高,但容量有限。
3. 在堆区分配:动态分配内存。用new/malloc时开辟,delete/free时释放。生存期由用户指定,灵活。但有内存泄露等问题。

 

24.类的初始化,用new和不用new的区别:
通过下面这个例子,会输出“释放1”,我们可以得出在不使用new创建对象时,对象的内存空间是在栈中的,其作用范围只是在函数内部,函数执行完成后就会调用析构函数,删除该对象。而使用new创建对象是创建在堆中的,必须要程序员手动的去管理该对象的内存空间

#include<iostream>
class TestNew
{
public:
	TestNew(int ID);
	~TestNew();
private:
	int ID;
};

TestNew::TestNew(int ID)
{
	this->ID = ID;//this就是TestNew类
}

TestNew::~TestNew()
{
	std::cout << "释放" << this->ID << std::endl;
}

void Test()
{
	TestNew test(1);
	TestNew * ptest = new TestNew(2);
}

int main()
{
    Test();
    return 0;
}


25.int *p=new int; 在堆中new一个int型 即4字节的内存空间,内存地址保存在p中
*p=3,     修改这块内存中存放的值,改为3
delete p;  释放这块内存空间,但是并没有释放p,p指针还能继续使用,
p=0;      一般是将p清0,以免错误
p=new int;   p还能继续使用
*p=400;       修改这块内存中存放的值,改为400


26.内存泄漏:因为new在堆中的内存不会自动释放,假如保存这块内存的指针是一个函数中的局部变量,当函数执行完毕后,会释放这个指针,那么这块内存就象消失了一样,再也找不回来了,这就叫内存泄漏,必须对这个指针使用delete

27.常见内存错误及对策
1. 内存分配未成功,却被使用。
对策:使用内存之前检查是否分配成功。用p!=NULL判断。
2. 内存分配成功,未初始化就被使用。
内存的缺省值没有统一的标准。大部分编译器以0作为初始值,但不完全是。
对策:内存初始化时赋初值。
3. 内存操作越界。
对策:只能是小心了。
4. 释放了内存,仍然使用。
(1) 使用显示delete和free的野指针。
对策:释放完内存,将指针置为NULL。
(2) 使用隐式delete和free的野指针。主要是指函数返回指向栈内存的指针或引用。
对策:当然是不要返回就可以了。
5. 未释放内存,导致内存泄露。
用new/malloc开辟了内存,没用delete/free释放.
对策:new和delete的个数一定相同;malloc和free的个数一定相同;new[]和[]delete一定对应

28.指针能加减能比较大小


29.将指针定义为常量指针 int *const p=&a;  p是一个常量指针,p本身的值无法修改,但是它指向的地址可以修改

30.假如把const放在前面  const int *p=&a;   p是指向常量的指针,p本身可以修改,但是p所指向的目标不能修改

31.函数的成员变量的初始化

class A
{
public:
   A():x(4),y(7)  //这是一种成员变量的初始化写法,会输出28
   {
    cout<<x*y
   };  
private:
   int x;
   int y;
}

如果把x定义为const的话,就只能使用这种写法给成员变量初始化,因为常量和引用只能被初始化不能被赋值。

 

posted @ 2017-11-15 09:57  birdhumen鸟人  阅读(234)  评论(0编辑  收藏  举报