c 语言过渡到 c++

  • 一、语法升级
  • 1、引用:
  • (1)、表达语句:&b = a 的形式,在c++语言中这种形似的写法,b是a的别名,b与a指向同一片地址,并且存有相同的值。
#include 

int main(){
	int a = 100;
	int &b = a;

	printf("a = %d\n", a);
	printf("b = %d\n", b);

	printf("add: a = %p\n", &a);
	printf("add: b = %p\n", &b);
/*
a = 100
b = 100
add: a = 0x7ffd3baf558c
add: b = 0x7ffd3baf558c
*/
}
  • (2)、应用:值交换函数,分别以错误写法、指针写法、引用写法表示
#include 

/*
void swp(int a, int b) {
	
	a ^= b;
	b ^= a;
	a ^= b;

}
/*
a = 100;
b = 10
*/
/*
void swp(int *p, int *q) {
	
	*p ^= *q;
	*q ^= *p;
	*p ^= *q;

}
*/

void swp(int &a, int &b) {

	a ^= b;
	b ^= a;
	a ^= b;
}

int main() {

	int a = 100;
	int b = 10;
	
	swp(a, b);

	printf("a = %d\n", a);
	printf("b = %d\n", b);
	
}
/*
a = 10
b = 100
*/
  • 2、默认参数:
  • C++语言中,可以在自定义函数中定义默认参数,在没有参数传进时,函数中使用这个默认参数,但是当有参数传入时使用传入的参数,要注意的是,当函数中使用多个参数时要将默认参数定义在后面,否则函数不知道传入的是默认参数还是其他参数。
#include 

void debug(const char *ptr = "------------") {
	printf("%s\n", ptr);
}

int main() {
	
	debug();
	debug();
	debug();

	debug("hello");
	debug("boy");
}
/*
------------
------------
------------
hello
boy
*/
  • 3、函数重载:在C++语言中两个自定义的函数可以使用同样的函数名,可以用 来同一功能,如作为比较函数,但是数的比较和字符串的比较,函数内容不一样,为了使用同一个函数名都用来表示比较,可以使用函数重载的方法。特别要注意的是,函数重载的两个函数的参数一定不能相同,要不然编译器不知道具体应该使用哪个函数。
#include 
#include 

int cmp(int a, int b) {

	return a-b;
}

int cmp(const char *str1, const char *str2) {

	return strcmp(str1, str2);
}

int main() {

	printf("%d\n", cmp(1, 2));
	printf("%d\n", cmp("bbb", "aaa"));

}
/*
-1
1
*/
  • 4、内存的申请与释放:与c语言中的malloc函数不一样,不需要再强制转化返回值,申请与释放的相对方便,但是释放一堆内存时需要在delete 后面加上[ ]在加上释放的目标。
#include 
#include 

int main() {

	int *intp = new int;

	*intp = 100;
	printf("*intp = %d\n", *intp);
	delete intp;

	char *p = new char[10];
	strcpy(p, "hello");
	printf("p: %s\n", p);
	delete [] p;
}
/*
*intp = 100
p: hello

*/

二、引入类与对象

  • 假如现在我要实现一个数组,并且可以随时在尾部进行插入,每次插入都是在上一次插入的末尾,现在我用c语言普通写法、进阶写法、再进阶写法、c++写法4中方法分别实现这个功能,来分析每种写法的缺点,然后引入c++类的写法。
  • 1、c语言普通写法
  • 缺点:可移植性太差,函数没有封装
#include 

int main() {
	
	int arr[100] = {0};
	int tail = 0;

	int n = 10;
	while (n--) {
		arr[tail++] = n;
	}

	int i = 0;
	for (i = 0; i < tail; i++) {
		printf("%d ", arr[i]);
	}
	
	printf("\n");

	n = 5;
	while (n--) {
		arr[tail++] = n;
	}

	for (i = 0; i < tail; i++) {
		printf("%d ", arr[i]);
	}

	printf("\n");

	return 0;
}
/*
9 8 7 6 5 4 3 2 1 0 
9 8 7 6 5 4 3 2 1 0 4 3 2 1 0 
*/
  • 2、c语言进阶写法
  • 缺点:虽然数据用了结构体封装,但是函数与数据并没有一个很好的绑定关系。什么意思呢?我们看main函数中,调用show函数时show(&arr)就可以了,但这是我写的函数,我知道要这样调用,但是当别人来看这个main函数时会不会有疑问:arr与show这个函数有什么关系,怎们知道就是这样用的,为什么arr不是放在其他函数里面进行尾部插入呢?所以整体来说,这样的写法在面向对象来说还不够好。
// array.h
#ifndef _ARR_
#define _ARR_

#define N 100

typedef struct arr{
	int data[N];
	int tail;
}ARR;

void init(ARR *arr);
void addtail(ARR *arr, int value);
void show(ARR *arr);

#endif

//array.c
#include "array.h"
#include 

void init(ARR *arr) {
	arr->tail = 0;
}

void addtail(ARR *arr, int value) {
	arr->data[arr->tail++] = value;
}

void show(ARR *arr) {
	int i = 0;
	for (i = 0; i < arr->tail; i++) {
		printf("%d ", arr->data[i]);
	}
}

//main.c
#include 
#include "array.h"

int main() {
	ARR arr;

	init(&arr);

	int n = 10;
	while (n--) {
		addtail(&arr, n);
	}

	show(&arr);
    printf("\n");

	return 0;
}
/*
9 8 7 6 5 4 3 2 1 0
*/
  • 3、针对上面提出的疑问,这个功能c语言还可以写成这样:
  • array.h,将函数也封装在结构体当中,但是init函数不能封装在结构体当中,因为我们需要一个初始化函数去初始化结构体当中的数据,并且结构体函数与具体的使用函数链接在一起。
#ifndef _ARR_
#define _ARR_

#define N 100

typedef struct arr{
	int data[N];
	int tail;

	void (*show)(struct arr *arr);
	void (*addtail)(struct arr *arr, int value);

}ARR;
void init(struct arr *arr);
#endif
  • array.c 要注意的是函数直接定义,所以show函数与addtail函数都应该放在init函数前面。要用static修饰,避免在其他地方也被随便调用。
#include "array.h"
#include 
static void addtail(ARR *arr, int value) {
	
	arr->data[arr->tail++] = value;
}

static void show(ARR *arr) {

	int i = 0;
	for (i = 0; i < arr->tail; i++) {
		printf("%d ", arr->data[i]);
	}
}

void init(ARR *arr) {	
	arr->tail = 0;
	arr->addtail = addtail;
	arr->show = show;
}
  • main.c
#include 
#include "array.h"

int main() {
	ARR arr;

	init(&arr);

	int n = 10;
	while (n--) {
		arr.addtail(&arr, n);
	}

	arr.show(&arr);
    printf("\n");

	return 0;
}
/*
9 8 7 6 5 4 3 2 1 0 
*/
  • 优点:实现了基本面向对象的框架,如linux内核也是用这种方法写的。在学linux驱动时也经常使用这种写法。
  • 缺点:要是我们在arr.show()函数前面写arr.tail=0;这种写法没有错,数组里面实际是有值的,但是因为show函数需要调用arr.tail的值,那么这个show函数就无法正确的表示出这个数组内的值了。但是c语言只能写到这个地步了,这就体现c语言写面对对象时的缺点,于是引入c++的类与方法,便能很好的解决这个问题。
  • 4、c++的解决办法:引入类
  • (1)、类的基本知识:类内部可以分成三个区,分别是public、private、protected。在private中定义的数据无法在类外面直接调用,可以在public写接口函数获得private中数据的值。其中类有构造函数与析构函数,构造函数是指当在其他函数中调用类时会调用的第一个函数,这个函数可以自己在类里面定义,实现一些初始化的功能或者输出一些提示信息,既然是函数就可以有参数或者无参数构造函数,当自己没有定义这个构造函数,类也会自动生成这个构造函数,只是没有一点提示信息。析构函数与构造函数相对应,在类的调用结束后使用,在函数中声明一个对象,其实在内存中相当与申请堆栈内存,所以在对象使用结束之后,用析构函数去释放。类的定义与调用具体用法如下面的代码:
#include 

class A{
public:
	//无参构造函数
	A() {
		printf("construct....\n");
		a = 0;
	}
	//有参构造参数
	A(int data) {
		a = data;
	}
	//析构函数
	~A() {
		printf("done!!!!\n");
	}
	void show() {
		printf("xxxxxxx\n");
	}

	void setdata(int data) {
		a = data;
	
	}

	int getdata(void);

private:
	int a;

};

int A::getdata(void) {

	return a;
}


int main() {

	A x;
	A y(1000);

	printf("x.a:  %d\n", x.getdata());
	printf("y.a:  %d\n", y.getdata());
	x.show();

	return 0;
}
/*
construct....
x.a:  0
y.a:  1000
xxxxxxx
done!!!!
done!!!!

*/
  • (2)用c++解决数组插入问题:
  • 注意:可以在类中写了接口,具体的实现可以在类外实现,只需要在写成ARR::函数 这种形式就可以了,可以在这个函数里直接调用类的私有数据。还要注意的一点是当类中定义的数据名与所需要接收的形参名一样时,需要用到this指针,加载数据名前就可以表示为这个数据是属于类的,不会让编译器犯迷糊。
// array.h
#ifndef _ARR_
#define _ARR_

class ARR{
public:
	ARR():tail(0) {
	
	}

	void addtail(int value);
	void show(void);

private:
	int data[100];
	int tail;
};
#endif

// array.cpp
#include "array.h"
#include 

void ARR::addtail(int data) {
	
	this->data[tail++] = data;
}

void ARR::show() {

	int i = 0;
	for (i = 0; i < tail; i++) {
		printf("%d ",data[i]);
	}
	printf("\n");
}

// main.cpp
#include 
#include "array.h"

int main() {
	ARR arr;

	arr.addtail(12);
	arr.addtail(14);
	arr.addtail(17);

	arr.show();

	return 0;
}

  • 三、类的成员函数:
  • 1、引入陷阱
#include 

class A{
public:
	A(){
			printf("A()\n");
			p = new char[10];
		}

	~A() {
		printf("~A()\n");
		delete [] p;
	}

private:
	char *p;

};


int main() {
	A x;
	A y = x;

	return 0;
}
/*
A()
~A()
~A()
free(): double free detected in tcache 2
已放弃 (核心已转储)
*/
  • 上诉的代码在编译时没有出错,但是在执行时除了错误,这是因为什么呢?我们都知道在申明 A x或者 A y=x时,堵在内存中申请了一段内村分别用于存放x和y,但是在A这个类中的构造函数中有为指针申请一个内存,x和y中的p都同时指向了一个地址,且保存相同的值,而在类中有析构函数去释放这个p所指向的内存,所以p所指向的内存在调用结束后被释放了两次,这样就会导致程序出现段错误,为了解决这个办法,我们这样修改代码:
#include 
#include 


class A{
public:
	A(){
			printf("A()\n");
			p = new char[10];
			strcpy(p, "hello");
			printf("P: %s\n", this->p);
		}

	A(const A &x) {
		printf("A(const A &x)\n");
		p = new char[10];
		strcpy(p, x.p);
		printf("P: %s\n", this->p);
	}


	~A() {
		printf("~A()\n");
		delete [] p;
	}

private:
	char *p;

};


int main() {
	A x;
	A y = x;

	return 0;
}
/*
A()
P: hello
A(const A &x)
P: hello
~A()
~A()
*/
  • A y=x;中y调用A(const A &x)这个析构函数,这样两个p指向的内存就不是同一个地方了,再通过strcpy(p, x.p)赋值,使两个p保存有同样的值。注意的时&x与具体函数中定义的x并没有关系。
  • 四、常成员、常对象
  • 1、在c++中推荐使用const,const数据成员只在某个对象生存期内是常量,而对于整个类来说确实可变的。static除外。在你某个对象中,你第一次定义了这个常数据成员,那么这个成员在这个对象中就不会改变,你要是新声明新对象,又可以重新定义这个对象的值。
  • 2、常数据成员,需要构造函数初始化表赋值,只能采用这种方法。const int x; A()::x(100);
  • 3、常成员函数 void func() const;指的是这个函数里面的内容不会随便修改,可以大胆使用,如之前使用的show函数,就可以用常成员函数。
  • 4、常对象。
// 
#include 

class A{
public:
	A(int a = 50, int data = 1000):b(data) {  //b只能在这声明,不能在对象中声明。
		this->a = a;   // this指针的使用,区分类的数据与参数。
		printf("AAAAAAAA\n");
	}

	~A() {
		printf("----------\n");
	}

	void show() {
		printf("b = %d\n", b);
		printf("a = %d\n", a);
	}

private:
	int a;
	const int b;

};

int main() {
	
	A x(10); // default parameter of a
	x.show();

	A y(100);
	y.show();

	A z; // a:50 b:1000
	z.show(); // 默认参数的用法

	return 0;
}
/*
AAAAAAAA
b = 1000
a = 10
AAAAAAAA
b = 1000
a = 100
AAAAAAAA
b = 1000
a = 50
----------
----------
----------
*/
  • static修饰的意思是,这个数据是属于类的,是所有基于这个类的对象所共有的的,在对象中这个值不可以改变。无需定义对象也可以使用,使用如以下代码:
#include 

class A{
public:
	static void func(void) {
		printf("xxxxxxx\n");
	}

	static int data;
};

int A::data = 10; //要单独在类外面声明以下这个数据,否则无法使用,与static函数的区别。

int main() {
	A a;
	a.func();
	A::func();

	A x;
	x.data = 100;
	printf("x.data = %d\n", x.data);

	A::data = 1000;
	printf("x.data = %d\n", x.data);

	return 0;
}
/*
xxxxxxx
xxxxxxx
x.data = 100
x.data = 1000
*/
  • 五、友元:友元类、友元函数、友元成员函数
  • 1、友元引入:有些时候我们要打破类的封装,如我们要实现基于之前数组插入的那个代码,再插入以后实现数组的成员的倒序排序。
// array.h
#ifndef _ARR_
#define _ARR_


class ARR{
public:
	ARR():tail(0) {
	
	}

	void addtail(int value);
	void show(void);

	friend void rev(ARR &arr);

private:
	int data[100];
	int tail;
};

#endif

// array.cpp
#include "array.h"
#include 


void ARR::addtail(int data) {
	
	this->data[tail++] = data;
}

void ARR::show() {

	int i = 0;
	for (i = 0; i < tail; i++) {
		printf("%d ",data[i]);
	}
	printf("\n");
}


// main.cpp
#include 
#include "array.h"

void rev(ARR &arr) {
	
	int i = 0;
	for (;i < arr.tail/2; i++) {
		int tem = arr.data[i];
		arr.data[i] = arr.data[arr.tail - i - 1];
		arr.data[arr.tail - i -1] = tem;
	}
}

int main() {
	ARR arr;

	arr.addtail(12);
	arr.addtail(14);
	arr.addtail(17);

	arr.show();

	rev(arr);
	arr.show();

	return 0;
}
/*
12 14 17 
17 14 12
*/
  • 2、友元类:A与B互为友元类的话,A与B可以直接互相调数据。
#include 

class A;  // x需要在使用前声明一下。

class B{
public:
	void printfA(A &x);
};

class A{
public:
	A() {
	
		x = 100;
	}

friend class B;

private:
	int x;

};

void B::printfA(A &x) {
	printf("%d\n", x.x);
}

int main() {

	A a;
	B b;

	b.printfA(a);

	return 0;
}
// 100
  • 3、友元成员函数:注意&x是指定用法,与实际数据x与对象x都没有关系,代指基于这个类生成的所有对象。
#include 

class A;

class B{
public:
	void printfA(A &x);
};

class A{
public:
	A() {
	
		x = 100;
	}

friend void B::printfA(A &x); //

private:
	int x;

};

void B::printfA(A &x) {
	printf("%d\n", x.x);
}

int main() {

	A a;
	B b;

	b.printfA(a);

	return 0;
}
// 100

你可能感兴趣的:(c语言过渡到c++,c语言,c++,开发语言)